Hello World & Output
Hello, World
Console.WriteLine("Hello, World!"); Option Strict On
Imports System
Module HelloWorld
Sub Main()
Console.WriteLine("Hello, World!")
End Sub
End Module Visual Basic requires an explicit
Module containing a Sub Main() entry point — there are no top-level statements as in C# 9+. The Console class is the same .NET API in both languages; the syntax is identical. Option Strict On is the recommended mode — it prevents implicit narrowing conversions and enforces explicit type declarations.String interpolation
var name = "developer";
var score = 42;
Console.WriteLine($"Hello, {name}! Score: {score}"); Option Strict On
Imports System
Module InterpolationDemo
Sub Main()
Dim name As String = "developer"
Dim score As Integer = 42
Console.WriteLine($"Hello, {name}! Score: {score}")
End Sub
End Module Visual Basic uses the same
$"..." interpolation syntax as C# — with expressions inside {}. Format specifiers also work the same way: {score:D4} for zero-padded integers, {ratio:F2} for two decimal places. This is one of the few syntax areas where C# and Visual Basic are identical.Multiple output / print formats
Console.WriteLine(42);
Console.WriteLine(3.14);
Console.WriteLine(true);
Console.Write("no newline");
Console.WriteLine(" — then newline"); Option Strict On
Imports System
Module OutputDemo
Sub Main()
Console.WriteLine(42)
Console.WriteLine(3.14)
Console.WriteLine(True)
Console.Write("no newline")
Console.WriteLine(" — then newline")
End Sub
End Module Console.WriteLine() and Console.Write() behave identically in both languages. Boolean literals are True and False (capitalized) in Visual Basic, unlike C#'s lowercase true and false. Parentheses are required for all method calls in Visual Basic — omitting them causes a compile error with Option Strict On.Program Structure
Entry point
// C# 9+ top-level statements: no class or Main() needed
Console.WriteLine("Entry point is implicit"); Option Strict On
Imports System
Module EntryPointDemo
Sub Main()
' Visual Basic always requires an explicit Sub Main()
Console.WriteLine("Entry point is explicit")
End Sub
End Module Visual Basic has no equivalent of C#'s top-level statements. Every executable Visual Basic program requires a
Module with a Sub Main(). The module name is arbitrary. With the project default settings, the compiler infers that Sub Main() is the entry point; you may also specify it explicitly in the project file.Imports vs using
using System.IO;
// System, LINQ, and Collections are available via implicit usings
var path = Path.Combine("folder", "file.txt");
Console.WriteLine(path); Option Strict On
Imports System
Imports System.IO
Module ImportsDemo
Sub Main()
' Imports brings namespaces into scope — same concept as C# using
Dim filePath As String = Path.Combine("folder", "file.txt")
Console.WriteLine(filePath)
End Sub
End Module Visual Basic uses
Imports where C# uses using. In .NET 6+ console projects, C# enables implicit usings for System, System.Collections.Generic, System.Linq, and System.Text — but System.IO still needs an explicit using. Visual Basic has no implicit imports; every namespace must be declared. Visual Basic also supports import aliasing: Imports Col = System.Collections.Generic.Module vs static class
Console.WriteLine(MathHelpers.Square(5));
Console.WriteLine(MathHelpers.Hypotenuse(3, 4));
// C#: static class acts as a container for static members
static class MathHelpers
{
public static int Square(int number) => number * number;
public static double Hypotenuse(double legA, double legB)
=> Math.Sqrt(legA * legA + legB * legB);
} Option Strict On
Imports System
Module MathHelpers
Function Square(number As Integer) As Integer
Return number * number
End Function
Function Hypotenuse(legA As Double, legB As Double) As Double
Return Math.Sqrt(legA * legA + legB * legB)
End Function
End Module
Module Program
Sub Main()
Console.WriteLine(MathHelpers.Square(5))
Console.WriteLine(MathHelpers.Hypotenuse(3, 4))
End Sub
End Module A Visual Basic
Module is the direct equivalent of a C# static class — all its members are implicitly shared (static) and the module itself cannot be instantiated. Members of a module in scope are callable without a qualifier, unlike a static class where you always prefix the class name. A project can contain multiple modules.Line continuation
// C#: implicit — expressions continue naturally across lines
var total = 1 + 2 + 3
+ 4 + 5;
Console.WriteLine(total); Option Strict On
Imports System
Module LineContinuationDemo
Sub Main()
' Modern Visual Basic: implicit continuation after operators, commas, "("
Dim total As Integer = 1 + 2 + 3 +
4 + 5
Console.WriteLine(total)
' Explicit underscore continuation (legacy — still works)
Dim message As String = "Hello, " & _
"world!"
Console.WriteLine(message)
End Sub
End Module Modern Visual Basic (since Visual Studio 2010) supports implicit line continuation after operators, commas, open parentheses, and certain keywords — no special character needed. The explicit underscore (
_) continuation character from classic BASIC is still valid but is considered legacy style. Unlike C# which uses ; as a statement terminator, Visual Basic uses the newline itself, so a physical line break ends a statement by default.Variables & Types
Type inference (var vs Dim)
var greeting = "hello";
var count = 42;
var ratio = 3.14;
var active = true;
Console.WriteLine($"{greeting} {count} {ratio} {active}"); Option Strict On
Imports System
Module TypeInferenceDemo
Sub Main()
' With Option Infer On (the default), Dim infers the type from the initializer
Dim greeting = "hello"
Dim count = 42
Dim ratio = 3.14
Dim active = True
Console.WriteLine($"{greeting} {count} {ratio} {active}")
End Sub
End Module Visual Basic uses
Dim (short for "dimension", from classic BASIC) where C# uses var. With Option Infer On (the default in modern Visual Basic), the compiler infers the type from the initializer — the same as var in C#. Without an initializer, you must supply an explicit type. Option Strict On and Option Infer On can coexist.Explicit type declarations
int count = 42;
long bigNumber = 9_000_000_000L;
double ratio = 3.14;
string message = "hello";
bool active = true;
Console.WriteLine($"{count} {bigNumber} {ratio} {message} {active}"); Option Strict On
Imports System
Module ExplicitTypesDemo
Sub Main()
Dim count As Integer = 42
Dim bigNumber As Long = 9_000_000_000L
Dim ratio As Double = 3.14
Dim message As String = "hello"
Dim active As Boolean = True
Console.WriteLine($"{count} {bigNumber} {ratio} {message} {active}")
End Sub
End Module Visual Basic type names differ from C#'s aliases:
Integer (not int), Long (not long), Double (not double), String (not string), Boolean (not bool). The underlying .NET types are identical — Integer is System.Int32 just like C#'s int. Numeric literals support underscore separators (9_000_000_000L) in both languages.Constants
const double Gravity = 9.81;
const int MaxPlayers = 8;
Console.WriteLine($"Gravity: {Gravity}, Max players: {MaxPlayers}"); Option Strict On
Imports System
Module ConstantsDemo
Const Gravity As Double = 9.81
Const MaxPlayers As Integer = 8
Sub Main()
Console.WriteLine($"Gravity: {Gravity}, Max players: {MaxPlayers}")
End Sub
End Module Visual Basic uses
Const (same keyword as C#) for compile-time constants, but the syntax puts As Type after the name: Const Name As Type = value. Module-level constants are accessible from all methods in the module. By convention, Visual Basic uses PascalCase for constant names rather than the ALL_CAPS convention common in C#.Nothing vs null
string? message = null;
if (message == null)
Console.WriteLine("message is null");
message = "hello";
Console.WriteLine(message ?? "fallback"); Option Strict On
Imports System
Module NothingDemo
Sub Main()
Dim message As String = Nothing
If message Is Nothing Then
Console.WriteLine("message is Nothing")
End If
message = "hello"
' If() with two arguments is the null-coalescing operator
Console.WriteLine(If(message, "fallback"))
End Sub
End Module Visual Basic uses
Nothing where C# uses null. Testing for Nothing uses Is Nothing or IsNot Nothing — never = Nothing (which compares value equality). The two-argument form If(value, fallback) is the null-coalescing operator, equivalent to C#'s ??. With Option Strict On, assigning Nothing to a value type initializes it to its default value.Nullable value types
int? score = null;
score = 95;
Console.WriteLine(score.HasValue);
Console.WriteLine(score.Value);
Console.WriteLine(score ?? 0); Option Strict On
Imports System
Module NullableDemo
Sub Main()
Dim score As Integer? = Nothing
score = 95
Console.WriteLine(score.HasValue)
Console.WriteLine(score.Value)
Console.WriteLine(If(score, 0))
End Sub
End Module Nullable value types use the same
? suffix syntax in both languages: Integer? in Visual Basic, int? in C#. Both compile to Nullable(Of T) / Nullable<T> under the hood. The .HasValue and .Value properties are identical. Visual Basic uses If(score, 0) for null-coalescing where C# uses score ?? 0.Type conversion / casting
string text = "42";
int number = int.Parse(text);
double ratio = (double)number;
Console.WriteLine($"{number} {ratio}");
bool parsed = int.TryParse("bad", out int result);
Console.WriteLine(parsed); Option Strict On
Imports System
Module TypeConversionDemo
Sub Main()
Dim text As String = "42"
Dim number As Integer = Integer.Parse(text)
Dim ratio As Double = CDbl(number)
Console.WriteLine($"{number} {ratio}")
Dim result As Integer = 0
Dim success As Boolean = Integer.TryParse("bad", result)
Console.WriteLine(success)
End Sub
End Module Visual Basic uses conversion functions —
CInt(), CDbl(), CStr(), CBool(), CLng() — where C# uses casts like (int) or (double). These are checked conversions: invalid conversions throw InvalidCastException. The safe-parse pattern uses TryParse, which works the same way, but note that Visual Basic passes the output variable without a keyword — no out modifier.Strings
String concatenation
string first = "Hello";
string second = "world";
string combined = first + ", " + second + "!";
Console.WriteLine(combined); Option Strict On
Imports System
Module ConcatenationDemo
Sub Main()
Dim first As String = "Hello"
Dim second As String = "world"
Dim combined As String = first & ", " & second & "!"
Console.WriteLine(combined)
End Sub
End Module Visual Basic uses
& as the dedicated string concatenation operator — it always produces a string result regardless of operand types. The + operator also works between two strings but is ambiguous when one operand might be numeric (it could perform addition instead), so & is the idiomatic choice. This is a source of bugs when coming from C# where + is unambiguous for strings.Common string methods
string message = " Hello, World! ";
Console.WriteLine(message.Trim());
Console.WriteLine(message.Trim().ToUpper());
Console.WriteLine(message.Trim().ToLower());
Console.WriteLine(message.Trim().Contains("World"));
Console.WriteLine(message.Trim().Replace("World", "C#"));
Console.WriteLine(message.Trim().Length); Option Strict On
Imports System
Module StringMethodsDemo
Sub Main()
Dim message As String = " Hello, World! "
Console.WriteLine(message.Trim())
Console.WriteLine(message.Trim().ToUpper())
Console.WriteLine(message.Trim().ToLower())
Console.WriteLine(message.Trim().Contains("World"))
Console.WriteLine(message.Trim().Replace("World", "VB"))
Console.WriteLine(message.Trim().Length)
End Sub
End Module Visual Basic string methods are identical to C# — they are the same .NET
System.String API. Method names use PascalCase (Trim(), ToUpper(), Contains(), Replace()). The .Length property is a property, not a method — no parentheses. One difference: Visual Basic also exposes the classic BASIC string functions (Len(), UCase(), LCase(), Trim() as global functions), but the .NET methods are preferred.Substring and indexing
string language = "CSharp";
Console.WriteLine(language[0]);
Console.WriteLine(language.Substring(0, 2));
Console.WriteLine(language.IndexOf('h')); Option Strict On
Imports System
Module SubstringDemo
Sub Main()
Dim language As String = "VisualBasic"
Console.WriteLine(language(0))
Console.WriteLine(language.Substring(0, 6))
Console.WriteLine(language.IndexOf("B"c))
End Sub
End Module Visual Basic accesses individual characters with parentheses —
language(0) — not square brackets. Substring(startIndex, length) is 0-based and works the same as in C#. Character literals use the "B"c suffix (the c suffix distinguishes a Char literal from a one-character String). IndexOf() returns the 0-based position or -1 if not found.Verbatim / raw strings
// C#: verbatim string — backslashes are literal
string path = @"C:\Users\dev\file.txt";
string multiLine = @"line one
line two";
Console.WriteLine(path);
Console.WriteLine(multiLine); Option Strict On
Imports System
Module VerbatimDemo
Sub Main()
' Visual Basic has no verbatim @ prefix — backslash is always literal
Dim filePath As String = "C:\Users\dev\file.txt"
' Newlines are inserted with Environment.NewLine or vbCrLf
Dim multiLine As String = "line one" & Environment.NewLine & "line two"
Console.WriteLine(filePath)
Console.WriteLine(multiLine)
End Sub
End Module Visual Basic has no equivalent of C#'s
@"..." verbatim string literal. Backslashes in Visual Basic strings are literal — there is no escape character (unlike C# where \n is a newline inside a regular string literal). To embed a newline, use Environment.NewLine (cross-platform) or the legacy constant vbCrLf. C# 11 raw string literals ("""...""") are also not available in Visual Basic.Split and join
string csv = "apple,banana,cherry";
string[] fruits = csv.Split(',');
Console.WriteLine(fruits[0]);
Console.WriteLine(fruits.Length);
Console.WriteLine(string.Join(" | ", fruits)); Option Strict On
Imports System
Module SplitJoinDemo
Sub Main()
Dim csv As String = "apple,banana,cherry"
Dim fruits As String() = csv.Split(","c)
Console.WriteLine(fruits(0))
Console.WriteLine(fruits.Length)
Console.WriteLine(String.Join(" | ", fruits))
End Sub
End Module Split() and String.Join() are identical .NET APIs in both languages. The split delimiter uses the "x"c character literal syntax. Array access uses parentheses in Visual Basic: fruits(0) not fruits[0]. This parenthesis convention for both method calls and array/index access is one of the most visually jarring differences for C# developers.Numbers & Math
Numeric types
byte smallByte = 255;
int count = 2_147_483_647;
long bigNumber = 9_000_000_000L;
float ratio = 3.14f;
double precise = 3.141592653589793;
decimal money = 99.99m;
Console.WriteLine($"{smallByte} {count} {bigNumber} {ratio} {precise} {money}"); Option Strict On
Imports System
Module NumericTypesDemo
Sub Main()
Dim smallByte As Byte = 255
Dim count As Integer = 2_147_483_647
Dim bigNumber As Long = 9_000_000_000L
Dim ratio As Single = 3.14F
Dim precise As Double = 3.141592653589793
Dim money As Decimal = 99.99D
Console.WriteLine($"{smallByte} {count} {bigNumber} {ratio} {precise} {money}")
End Sub
End Module Visual Basic type names for numbers:
Byte, Short (16-bit), Integer (32-bit), Long (64-bit), Single (32-bit float), Double (64-bit float), Decimal. Numeric literal suffixes: L for Long, F for Single, D for Decimal — the same as C# except Visual Basic uses F for Single where C# uses f for float.Math operations
Console.WriteLine(Math.Abs(-5));
Console.WriteLine(Math.Pow(2, 10));
Console.WriteLine(Math.Sqrt(144.0));
Console.WriteLine(Math.Round(3.7));
Console.WriteLine(Math.Max(10, 20));
Console.WriteLine(17 % 5); Option Strict On
Imports System
Module MathOperationsDemo
Sub Main()
Console.WriteLine(Math.Abs(-5))
Console.WriteLine(Math.Pow(2, 10))
Console.WriteLine(Math.Sqrt(144.0))
Console.WriteLine(Math.Round(3.7))
Console.WriteLine(Math.Max(10, 20))
Console.WriteLine(17 Mod 5)
End Sub
End Module The
Math class is identical in both languages. The key difference is the modulo operator: Visual Basic uses the Mod keyword where C# uses %. Visual Basic also has a built-in exponentiation operator ^ — 2 ^ 10 returns 1024.0 as a Double — whereas C# must call Math.Pow() for this.Integer division and rounding
int quotient = 17 / 5;
int remainder = 17 % 5;
double divided = 17.0 / 5;
Console.WriteLine($"{quotient} remainder {remainder}");
Console.WriteLine(divided); Option Strict On
Imports System
Module IntegerDivisionDemo
Sub Main()
Dim quotient As Integer = 17 \ 5
Dim remainder As Integer = 17 Mod 5
Dim divided As Double = 17.0 / 5
Console.WriteLine($"{quotient} remainder {remainder}")
Console.WriteLine(divided)
End Sub
End Module Visual Basic distinguishes integer division (
\ backslash) from floating-point division (/ forward slash). In C#, dividing two integers with / automatically performs integer division. In Visual Basic, / always produces a Double — you must use \ to get integer division. This is a subtle source of type mismatch errors when porting C# code.Collections
Arrays
int[] numbers = { 10, 20, 30, 40, 50 };
Console.WriteLine(numbers[0]);
Console.WriteLine(numbers.Length);
int[,] grid = new int[3, 3];
grid[1, 1] = 9;
Console.WriteLine(grid[1, 1]); Option Strict On
Imports System
Module ArraysDemo
Sub Main()
Dim numbers As Integer() = {10, 20, 30, 40, 50}
Console.WriteLine(numbers(0))
Console.WriteLine(numbers.Length)
Dim grid(2, 2) As Integer
grid(1, 1) = 9
Console.WriteLine(grid(1, 1))
End Sub
End Module Visual Basic array syntax differs in two important ways: array elements are accessed with parentheses (
numbers(0) not numbers[0]), and Dim grid(2, 2) creates a 3×3 array — the number specifies the upper bound, not the length. Dim grid(2, 2) has indices 0–2 in each dimension. Arrays are fixed-size once declared; use List(Of T) for dynamic resizing.List<T> vs List(Of T)
var fruits = new List<string> { "apple", "banana", "cherry" };
fruits.Add("date");
fruits.Remove("banana");
Console.WriteLine(fruits.Contains("cherry"));
Console.WriteLine(fruits.Count);
foreach (var fruit in fruits)
Console.WriteLine(fruit); Option Strict On
Imports System
Imports System.Collections.Generic
Module ListDemo
Sub Main()
Dim fruits As New List(Of String) From {"apple", "banana", "cherry"}
fruits.Add("date")
fruits.Remove("banana")
Console.WriteLine(fruits.Contains("cherry"))
Console.WriteLine(fruits.Count)
For Each fruit As String In fruits
Console.WriteLine(fruit)
Next
End Sub
End Module Visual Basic uses
List(Of T) where C# uses List<T> — the generic syntax uses (Of T) instead of angle brackets. The initializer uses From { ... } (with New) instead of C#'s { ... }. All methods (Add, Remove, Contains) are identical. Note .Count not .Length — arrays use .Length, collections use .Count.Dictionary<K,V> vs Dictionary(Of K, V)
var scores = new Dictionary<string, int>
{
["Alice"] = 95,
["Bob"] = 87,
};
scores["Charlie"] = 92;
Console.WriteLine(scores["Alice"]);
Console.WriteLine(scores.ContainsKey("Bob"));
foreach (var pair in scores)
Console.WriteLine($"{pair.Key}: {pair.Value}"); Option Strict On
Imports System
Imports System.Collections.Generic
Module DictionaryDemo
Sub Main()
Dim scores As New Dictionary(Of String, Integer) From {
{"Alice", 95},
{"Bob", 87}
}
scores("Charlie") = 92
Console.WriteLine(scores("Alice"))
Console.WriteLine(scores.ContainsKey("Bob"))
For Each pair As KeyValuePair(Of String, Integer) In scores
Console.WriteLine($"{pair.Key}: {pair.Value}")
Next
End Sub
End Module Visual Basic uses
Dictionary(Of K, V) and accesses elements with parentheses (scores("Alice")) instead of C#'s square brackets (scores["Alice"]). The collection initializer uses From { {key, value}, ... } syntax. Iteration exposes KeyValuePair(Of K, V) entries with .Key and .Value properties — identical to C#.Array / collection methods
int[] numbers = { 3, 1, 4, 1, 5, 9, 2, 6 };
Array.Sort(numbers);
Console.WriteLine(string.Join(", ", numbers));
Console.WriteLine(Array.IndexOf(numbers, 5));
Console.WriteLine(numbers.Min());
Console.WriteLine(numbers.Max()); Option Strict On
Imports System
Imports System.Linq
Module ArrayMethodsDemo
Sub Main()
Dim numbers As Integer() = {3, 1, 4, 1, 5, 9, 2, 6}
Array.Sort(numbers)
Console.WriteLine(String.Join(", ", numbers))
Console.WriteLine(Array.IndexOf(numbers, 5))
Console.WriteLine(numbers.Min())
Console.WriteLine(numbers.Max())
End Sub
End Module Array.Sort(), Array.IndexOf(), and LINQ extension methods (.Min(), .Max(), .Sum()) work identically in both languages. The LINQ extension methods require Imports System.Linq in Visual Basic, whereas C# projects include it automatically via implicit usings. String.Join() is a static method call in both languages.HashSet
var unique = new HashSet<string> { "cat", "dog", "cat" };
unique.Add("bird");
Console.WriteLine(unique.Count);
Console.WriteLine(unique.Contains("dog")); Option Strict On
Imports System
Imports System.Collections.Generic
Module HashSetDemo
Sub Main()
Dim unique As New HashSet(Of String) From {"cat", "dog", "cat"}
unique.Add("bird")
Console.WriteLine(unique.Count)
Console.WriteLine(unique.Contains("dog"))
End Sub
End Module HashSet(Of T) in Visual Basic corresponds to HashSet<T> in C# — both guarantee uniqueness and provide O(1) average-time lookups. Duplicate values added via Add() are silently ignored. The generic (Of T) syntax applies to all generic types across the framework: Stack(Of T), Queue(Of T), IEnumerable(Of T), and so on.Control Flow
If / Else If / Else
int temperature = 22;
if (temperature > 30)
Console.WriteLine("hot");
else if (temperature > 20)
Console.WriteLine("warm");
else
Console.WriteLine("cool"); Option Strict On
Imports System
Module IfElseDemo
Sub Main()
Dim temperature As Integer = 22
If temperature > 30 Then
Console.WriteLine("hot")
ElseIf temperature > 20 Then
Console.WriteLine("warm")
Else
Console.WriteLine("cool")
End If
End Sub
End Module Visual Basic uses
ElseIf (one word) where C# uses else if (two words). Every multi-line If block requires Then after the condition and closes with End If. Single-line form is available for simple cases: If condition Then statement. There are no curly braces — indentation is cosmetic, not syntactic.Select Case vs switch
int day = 3;
string name = day switch
{
1 => "Monday",
2 => "Tuesday",
3 => "Wednesday",
4 => "Thursday",
5 => "Friday",
_ => "Weekend",
};
Console.WriteLine(name); Option Strict On
Imports System
Module SelectCaseDemo
Sub Main()
Dim day As Integer = 3
Dim dayName As String
Select Case day
Case 1
dayName = "Monday"
Case 2
dayName = "Tuesday"
Case 3
dayName = "Wednesday"
Case 4
dayName = "Thursday"
Case 5
dayName = "Friday"
Case Else
dayName = "Weekend"
End Select
Console.WriteLine(dayName)
End Sub
End Module Visual Basic
Select Case is more powerful than C# switch in its traditional form — each Case can list multiple values (Case 1, 3, 5), ranges (Case 1 To 5), and relational tests (Case Is > 10). Cases do not fall through by default. C# pattern-matching switch expressions are more concise for simple mappings, but Visual Basic's range syntax has no C# equivalent without additional logic.Ternary / conditional expression
int score = 75;
string grade = score >= 60 ? "pass" : "fail";
Console.WriteLine(grade); Option Strict On
Imports System
Module TernaryDemo
Sub Main()
Dim score As Integer = 75
Dim grade As String = If(score >= 60, "pass", "fail")
Console.WriteLine(grade)
End Sub
End Module Visual Basic uses the
If(condition, trueValue, falseValue) function as a ternary operator — there is no ?: syntax. Unlike the older IIf() function (which always evaluated both branches and was not type-safe), If() short-circuits and is type-safe. The two-argument form If(value, fallback) is a null-coalescing operator equivalent to C#'s ??.Logical operators (critical gotcha)
bool active = true;
bool authorized = false;
// C#: && and || are always short-circuit
if (active && authorized)
Console.WriteLine("both true");
if (active || authorized)
Console.WriteLine("at least one true"); Option Strict On
Imports System
Module LogicalOperatorsDemo
Sub Main()
Dim active As Boolean = True
Dim authorized As Boolean = False
' AndAlso and OrElse are short-circuit (like C# && and ||)
If active AndAlso authorized Then
Console.WriteLine("both true")
End If
If active OrElse authorized Then
Console.WriteLine("at least one true")
End If
' And and Or are NON-short-circuit — they evaluate both sides always
' (also used as bitwise operators on integers)
End Sub
End Module This is the most important gotcha when moving from C# to Visual Basic.
AndAlso and OrElse are the short-circuit logical operators (equivalent to C#'s && and ||). And and Or are non-short-circuit — they evaluate both operands always, and they also function as bitwise operators on integers. Always use AndAlso/OrElse in boolean logic to avoid null-reference exceptions from the second operand.Equality — = vs == and Is
string language = "CSharp";
if (language == "CSharp")
Console.WriteLine("equal");
object boxed1 = 42;
object boxed2 = 42;
Console.WriteLine(object.ReferenceEquals(boxed1, boxed2)); Option Strict On
Imports System
Module EqualityDemo
Sub Main()
Dim language As String = "VisualBasic"
' = is used for both assignment AND equality comparison
If language = "VisualBasic" Then
Console.WriteLine("equal")
End If
' Is tests reference equality (like object.ReferenceEquals in C#)
Dim objectA As Object = "hello"
Dim objectB As Object = "hello"
Console.WriteLine(objectA Is objectB)
End Sub
End Module Visual Basic uses
= for both assignment and equality comparison — the context determines which meaning applies. There is no == operator. For reference equality (testing whether two variables point to the same object), Visual Basic uses Is and IsNot, which correspond to C#'s object.ReferenceEquals(). String comparison with = compares value (content), not reference.Loops & Iteration
Counted For loop
for (int index = 0; index < 5; index++)
Console.Write($"{index} ");
Console.WriteLine();
for (int count = 10; count >= 1; count--)
Console.Write($"{count} ");
Console.WriteLine(); Option Strict On
Imports System
Module ForLoopDemo
Sub Main()
For index As Integer = 0 To 4
Console.Write($"{index} ")
Next
Console.WriteLine()
For count As Integer = 10 To 1 Step -1
Console.Write($"{count} ")
Next
Console.WriteLine()
End Sub
End Module Visual Basic's
For...Next loop uses an inclusive upper bound — For index = 0 To 4 iterates 0, 1, 2, 3, 4. The Step keyword specifies the increment (default is 1); a negative Step counts down. The loop variable can be declared inline with As Type. There is no three-part for (init; condition; update) form — Visual Basic's For is always a counted range.foreach / For Each
var fruits = new List<string> { "apple", "banana", "cherry" };
foreach (var fruit in fruits)
Console.WriteLine(fruit); Option Strict On
Imports System
Imports System.Collections.Generic
Module ForEachDemo
Sub Main()
Dim fruits As New List(Of String) From {"apple", "banana", "cherry"}
For Each fruit As String In fruits
Console.WriteLine(fruit)
Next
End Sub
End Module Visual Basic uses
For Each...In...Next where C# uses foreach...in. The loop variable type is declared inline with As Type. Both iterate over any IEnumerable(Of T). One subtlety: For Each in Visual Basic allows the loop variable to be declared before the loop with Dim, or inline — either form works.While loop
int count = 0;
while (count < 5)
{
Console.WriteLine(count);
count++;
} Option Strict On
Imports System
Module WhileLoopDemo
Sub Main()
Dim count As Integer = 0
While count < 5
Console.WriteLine(count)
count += 1
End While
End Sub
End Module Visual Basic uses
While...End While where C# uses while { }. There is no ++ increment operator in Visual Basic — use count += 1 or count = count + 1. Alternatively, Do While condition...Loop and Do Until condition...Loop are also available — "Until" is the inverse of "While" and has no C# equivalent.Break and continue
for (int number = 1; number <= 10; number++)
{
if (number == 5) continue;
if (number == 8) break;
Console.Write($"{number} ");
}
Console.WriteLine(); Option Strict On
Imports System
Module BreakContinueDemo
Sub Main()
For number As Integer = 1 To 10
If number = 5 Then Continue For
If number = 8 Then Exit For
Console.Write($"{number} ")
Next
Console.WriteLine()
End Sub
End Module Visual Basic uses
Continue For / Continue Do / Continue While where C# uses a single continue keyword. Similarly, Exit For / Exit Do / Exit While replace C#'s single break. The keyword always names the loop type it applies to. Visual Basic does not support labeled loops for breaking out of nested loops — a Boolean flag or a helper function is the idiomatic workaround.Subroutines & Functions
Sub (void method)
void Greet(string name)
{
Console.WriteLine($"Hello, {name}!");
}
Greet("Alice"); Option Strict On
Imports System
Module SubVsVoidDemo
Sub Greet(name As String)
Console.WriteLine($"Hello, {name}!")
End Sub
Sub Main()
Greet("Alice")
End Sub
End Module A Visual Basic
Sub corresponds to a C# void method — it performs an action and returns no value. The name uses PascalCase by convention. Parameters are declared as name As Type (reversed from C#'s Type name). Subroutines defined in the same Module are called without a qualifier. There is no return statement in a Sub (though Return with no value exits early).Function (return value)
int Square(int number) => number * number;
double Hypotenuse(double legA, double legB)
=> Math.Sqrt(legA * legA + legB * legB);
Console.WriteLine(Square(5));
Console.WriteLine(Hypotenuse(3, 4)); Option Strict On
Imports System
Module FunctionReturnDemo
Function Square(number As Integer) As Integer
Return number * number
End Function
Function Hypotenuse(legA As Double, legB As Double) As Double
Return Math.Sqrt(legA * legA + legB * legB)
End Function
Sub Main()
Console.WriteLine(Square(5))
Console.WriteLine(Hypotenuse(3, 4))
End Sub
End Module A Visual Basic
Function corresponds to any C# method with a non-void return type. The return type comes at the end: Function Name(params) As ReturnType. The Return statement works the same as in C#. Visual Basic also supports assigning the return value to the function name (Square = number * number) — a classic BASIC pattern that still works but is considered legacy; prefer Return.Optional parameters and named arguments
void Greet(string name, string greeting = "Hello")
{
Console.WriteLine($"{greeting}, {name}!");
}
Greet("Alice");
Greet("Bob", "Hi");
Greet(greeting: "Hey", name: "Carol"); Option Strict On
Imports System
Module OptionalParamsDemo
Sub Greet(name As String, Optional greeting As String = "Hello")
Console.WriteLine($"{greeting}, {name}!")
End Sub
Sub Main()
Greet("Alice")
Greet("Bob", "Hi")
Greet(greeting:="Hey", name:="Carol")
End Sub
End Module Visual Basic requires the
Optional keyword before an optional parameter — C# just assigns a default value without a keyword. Named arguments use name:=value syntax (with :=) where C# uses name: value (with a colon only). Optional parameters must follow all required parameters, same as in C#.ByRef vs ref / out
void Swap(ref int first, ref int second)
{
int temp = first;
first = second;
second = temp;
}
int alpha = 1, beta = 2;
Swap(ref alpha, ref beta);
Console.WriteLine($"{alpha} {beta}"); Option Strict On
Imports System
Module ByRefDemo
Sub Swap(ByRef first As Integer, ByRef second As Integer)
Dim temp As Integer = first
first = second
second = temp
End Sub
Sub Main()
Dim alpha As Integer = 1
Dim beta As Integer = 2
Swap(alpha, beta)
Console.WriteLine($"{alpha} {beta}")
End Sub
End Module Visual Basic uses
ByRef where C# uses ref or out. The default is ByVal (pass by value), equivalent to C#'s default behavior. The key difference: C# requires the ref keyword at the call site too (Swap(ref alpha, ref beta)), but Visual Basic does not — you just pass the variable. Visual Basic does not have a separate out keyword; ByRef covers both use cases.Lambda expressions
Func<int, int> square = number => number * number;
Func<int, bool> isEven = number => number % 2 == 0;
Action<string> printer = message => Console.WriteLine(message);
Console.WriteLine(square(5));
Console.WriteLine(isEven(4));
printer("hello"); Option Strict On
Imports System
Module LambdaDemo
Sub Main()
Dim square As Func(Of Integer, Integer) = Function(number) number * number
Dim isEven As Func(Of Integer, Boolean) = Function(number) number Mod 2 = 0
Dim printer As Action(Of String) = Sub(message) Console.WriteLine(message)
Console.WriteLine(square(5))
Console.WriteLine(isEven(4))
printer("hello")
End Sub
End Module Visual Basic uses
Function(params) expression for lambdas that return a value, and Sub(params) statement for lambdas that return nothing. C# uses a single => arrow syntax for both. Multi-line lambdas are also supported with Function(x) ... End Function. Generic delegate types use (Of T): Func(Of Integer, Boolean) instead of Func<int, bool>.Classes & OOP
Class definition
var person = new Person("Alice", 30);
Console.WriteLine(person.Describe());
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public Person(string name, int age)
{
Name = name;
Age = age;
}
public string Describe() => $"{Name}, age {Age}";
} Option Strict On
Imports System
Class Person
Public Property Name As String
Public Property Age As Integer
Public Sub New(name As String, age As Integer)
Me.Name = name
Me.Age = age
End Sub
Public Function Describe() As String
Return $"{Name}, age {Age}"
End Function
End Class
Module Program
Sub Main()
Dim person As New Person("Alice", 30)
Console.WriteLine(person.Describe())
End Sub
End Module Visual Basic class syntax replaces curly braces with
End Class. Auto-implemented properties use Public Property Name As Type. The constructor is named Sub New (not the class name). Me is the equivalent of C#'s this. Method definitions are Sub (void) or Function...As ReturnType. Classes are defined outside the module and can coexist in the same file.Properties with Get / Set
var temp = new Temperature { Celsius = 100.0 };
Console.WriteLine(temp.Fahrenheit);
class Temperature
{
private double _celsius;
public double Celsius
{
get => _celsius;
set => _celsius = value >= -273.15 ? value : -273.15;
}
public double Fahrenheit => _celsius * 9.0 / 5.0 + 32;
} Option Strict On
Imports System
Class Temperature
Private _celsius As Double
Public Property Celsius As Double
Get
Return _celsius
End Get
Set(value As Double)
_celsius = If(value >= -273.15, value, -273.15)
End Set
End Property
Public ReadOnly Property Fahrenheit As Double
Get
Return _celsius * 9.0 / 5.0 + 32
End Get
End Property
End Class
Module Program
Sub Main()
Dim temp As New Temperature()
temp.Celsius = 100.0
Console.WriteLine(temp.Fahrenheit)
End Sub
End Module Visual Basic property accessors use
Get...End Get and Set(value As Type)...End Set blocks — more verbose than C#'s get => / set => arrow syntax. A read-only property uses the ReadOnly keyword and omits the Set block. Write-only properties use WriteOnly. Object initializers work with auto-properties using the familiar New T() With { .Prop = value } syntax.Inheritance
var dog = new Dog("Rex");
Console.WriteLine($"{dog.Name} says {dog.Speak()}");
class Animal
{
public string Name { get; }
public Animal(string name) { Name = name; }
public virtual string Speak() => "...";
}
class Dog : Animal
{
public Dog(string name) : base(name) {}
public override string Speak() => "Woof!";
} Option Strict On
Imports System
Class Animal
Public ReadOnly Property Name As String
Public Sub New(name As String)
Me.Name = name
End Sub
Public Overridable Function Speak() As String
Return "..."
End Function
End Class
Class Dog
Inherits Animal
Public Sub New(name As String)
MyBase.New(name)
End Sub
Public Overrides Function Speak() As String
Return "Woof!"
End Function
End Class
Module Program
Sub Main()
Dim dog As New Dog("Rex")
Console.WriteLine($"{dog.Name} says {dog.Speak()}")
End Sub
End Module Visual Basic uses
Inherits BaseClass as a statement inside the class body (not : BaseClass after the class name). Base constructor calls use MyBase.New(...) instead of C#'s base(...). Methods that can be overridden are marked Overridable (not virtual). Overriding methods require the Overrides keyword (same concept as C#, just spelled differently).Abstract classes
Shape shape = new Circle(5.0);
Console.WriteLine(shape.Describe());
abstract class Shape
{
public abstract double Area();
public string Describe() => $"Area: {Area():F2}";
}
class Circle : Shape
{
private double _radius;
public Circle(double radius) { _radius = radius; }
public override double Area() => Math.PI * _radius * _radius;
} Option Strict On
Imports System
MustInherit Class Shape
Public MustOverride Function Area() As Double
Public Function Describe() As String
Return $"Area: {Area():F2}"
End Function
End Class
Class Circle
Inherits Shape
Private _radius As Double
Public Sub New(radius As Double)
_radius = radius
End Sub
Public Overrides Function Area() As Double
Return Math.PI * _radius * _radius
End Function
End Class
Module Program
Sub Main()
Dim shape As Shape = New Circle(5.0)
Console.WriteLine(shape.Describe())
End Sub
End Module Visual Basic replaces C#'s
abstract keyword with two different keywords: MustInherit on the class declaration (the class cannot be instantiated) and MustOverride on individual methods (derived classes must provide an implementation). C#'s sealed (prevent further inheritance) becomes NotInheritable. A MustOverride method has no body — the declaration ends with End Function omitted, just the signature.Shared vs static members
new Counter(); new Counter(); new Counter();
Console.WriteLine(Counter.Total);
Counter.Reset();
Console.WriteLine(Counter.Total);
class Counter
{
private static int _total = 0;
public static int Total => _total;
public Counter() { _total++; }
public static void Reset() { _total = 0; }
} Option Strict On
Imports System
Class Counter
Private Shared _total As Integer = 0
Public Shared ReadOnly Property Total As Integer
Get
Return _total
End Get
End Property
Public Sub New()
_total += 1
End Sub
Public Shared Sub Reset()
_total = 0
End Sub
End Class
Module Program
Sub Main()
Dim c1 As New Counter()
Dim c2 As New Counter()
Dim c3 As New Counter()
Console.WriteLine(Counter.Total)
Counter.Reset()
Console.WriteLine(Counter.Total)
End Sub
End Module Visual Basic uses the
Shared keyword where C# uses static. A Shared member belongs to the class rather than any instance — the same concept. Shared members are accessed via the class name: Counter.Total. Note that modules in Visual Basic are implicitly shared — all module members are shared automatically, which is why modules are the common home for top-level procedures.Interfaces
Interface definition and implementation
IGreeter greeter = new FormalGreeter();
Console.WriteLine(greeter.Greet("Alice"));
interface IGreeter
{
string Greet(string name);
string DefaultGreet() => Greet("World");
}
class FormalGreeter : IGreeter
{
public string Greet(string name) => $"Good day, {name}.";
} Option Strict On
Imports System
Interface IGreeter
Function Greet(name As String) As String
End Interface
Class FormalGreeter
Implements IGreeter
Public Function Greet(name As String) As String Implements IGreeter.Greet
Return $"Good day, {name}."
End Function
End Class
Module Program
Sub Main()
Dim greeter As IGreeter = New FormalGreeter()
Console.WriteLine(greeter.Greet("Alice"))
End Sub
End Module Visual Basic interface implementation requires two steps: the class declaration includes
Implements IGreeter, and each implementing method ends with Implements IGreeter.MethodName. This explicit annotation allows the implementing method to have a different name from the interface method — unlike C# where the names must match for implicit implementation. Visual Basic does not support default interface methods (a C# 8 feature).Multiple interface implementation
var store = new DataStore();
store.Save();
store.Load();
interface ISaveable { void Save(); }
interface ILoadable { void Load(); }
class DataStore : ISaveable, ILoadable
{
public void Save() => Console.WriteLine("Saving...");
public void Load() => Console.WriteLine("Loading...");
} Option Strict On
Imports System
Interface ISaveable
Sub Save()
End Interface
Interface ILoadable
Sub Load()
End Interface
Class DataStore
Implements ISaveable, ILoadable
Public Sub Save() Implements ISaveable.Save
Console.WriteLine("Saving...")
End Sub
Public Sub Load() Implements ILoadable.Load
Console.WriteLine("Loading...")
End Sub
End Class
Module Program
Sub Main()
Dim store As New DataStore()
store.Save()
store.Load()
End Sub
End Module Implementing multiple interfaces lists them after a single
Implements statement: Implements ISaveable, ILoadable. Each method that satisfies an interface contract must carry an Implements InterfaceName.MethodName clause. This verbose annotation eliminates the ambiguity that C# resolves with explicit interface implementation (void IFoo.Method()) — in Visual Basic, every implementation is explicit.Error Handling
Try / Catch / Finally
try
{
int result = int.Parse("bad");
Console.WriteLine(result);
}
catch (FormatException ex)
{
Console.WriteLine($"Format error: {ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
finally
{
Console.WriteLine("Always runs");
} Option Strict On
Imports System
Module TryCatchDemo
Sub Main()
Try
Dim result As Integer = Integer.Parse("bad")
Console.WriteLine(result)
Catch formatException As FormatException
Console.WriteLine($"Format error: {formatException.Message}")
Catch generalException As Exception
Console.WriteLine($"Error: {generalException.Message}")
Finally
Console.WriteLine("Always runs")
End Try
End Sub
End Module Visual Basic uses
Try...Catch...Finally...End Try where C# uses try { } catch { } finally { }. The exception variable declaration syntax is reversed: Catch ex As FormatException instead of C#'s catch (FormatException ex). Multiple Catch blocks are supported; the first matching one executes. The Finally block always runs.Throwing exceptions
void Validate(int age)
{
if (age < 0 || age > 150)
throw new ArgumentOutOfRangeException(nameof(age), "Age must be 0-150.");
}
try { Validate(-1); }
catch (ArgumentOutOfRangeException ex)
{ Console.WriteLine(ex.Message); } Option Strict On
Imports System
Module ThrowDemo
Sub Validate(age As Integer)
If age < 0 OrElse age > 150 Then
Throw New ArgumentOutOfRangeException(NameOf(age), "Age must be 0-150.")
End If
End Sub
Sub Main()
Try
Validate(-1)
Catch exception As ArgumentOutOfRangeException
Console.WriteLine(exception.Message)
End Try
End Sub
End Module Visual Basic uses
Throw New ExceptionType(...) where C# uses throw new ExceptionType(...). The NameOf() operator works the same in both languages — it returns the name of a symbol as a compile-time string constant, preserving refactoring safety. Note the use of OrElse in the validation condition — Or would evaluate both sides even if the first is already True.Custom exception class
try { throw new ValidationException("Email", "Invalid format."); }
catch (ValidationException ex)
{ Console.WriteLine($"{ex.Field}: {ex.Message}"); }
class ValidationException : Exception
{
public string Field { get; }
public ValidationException(string field, string message)
: base(message)
{
Field = field;
}
} Option Strict On
Imports System
Class ValidationException
Inherits Exception
Public ReadOnly Property Field As String
Public Sub New(field As String, message As String)
MyBase.New(message)
Me.Field = field
End Sub
End Class
Module Program
Sub Main()
Try
Throw New ValidationException("Email", "Invalid format.")
Catch exception As ValidationException
Console.WriteLine($"{exception.Field}: {exception.Message}")
End Try
End Sub
End Module Custom exception classes inherit from
Exception using Inherits Exception. The base constructor is invoked with MyBase.New(message) — the Visual Basic equivalent of C#'s : base(message) initializer. Custom properties are declared with Public ReadOnly Property and assigned in Sub New. The overall pattern is structurally identical to C#, just with Visual Basic syntax throughout.LINQ
LINQ method syntax
var numbers = new List<int> { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
var evens = numbers.Where(number => number % 2 == 0);
var doubled = evens.Select(number => number * 2);
var total = numbers.Sum();
Console.WriteLine(string.Join(", ", doubled));
Console.WriteLine(total); Option Strict On
Imports System
Imports System.Collections.Generic
Imports System.Linq
Module LinqMethodDemo
Sub Main()
Dim numbers As New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
Dim evens = numbers.Where(Function(number) number Mod 2 = 0)
Dim doubled = evens.Select(Function(number) number * 2)
Dim total = numbers.Sum()
Console.WriteLine(String.Join(", ", doubled))
Console.WriteLine(total)
End Sub
End Module LINQ method syntax works in Visual Basic using lambdas with the
Function(x) keyword instead of C#'s x => arrow. The LINQ extension methods (Where, Select, Sum, OrderBy, GroupBy, etc.) are identical — they are the same .NET API. Imports System.Linq is required, whereas C# projects include it automatically via implicit usings.LINQ query syntax
var numbers = new[] { 5, 3, 8, 1, 9, 2, 7, 4, 6 };
var results = from number in numbers
where number > 4
orderby number descending
select number * 10;
foreach (var result in results)
Console.Write($"{result} ");
Console.WriteLine(); Option Strict On
Imports System
Imports System.Linq
Module LinqQueryDemo
Sub Main()
Dim numbers As Integer() = {5, 3, 8, 1, 9, 2, 7, 4, 6}
Dim results = From number In numbers
Where number > 4
Order By number Descending
Select number * 10
For Each result As Integer In results
Console.Write($"{result} ")
Next
Console.WriteLine()
End Sub
End Module Visual Basic has first-class support for LINQ query syntax — unlike C# where it is syntactic sugar over method calls, Visual Basic parses query keywords directly. The syntax differences:
From x In (not from x in), Order By (two words, not orderby), Descending (not descending). Visual Basic LINQ also supports Aggregate, Distinct, Skip, Take, and Let clauses.Group By
var words = new[] { "cat", "car", "bat", "bee", "cab" };
var groups = words.GroupBy(word => word[0]);
foreach (var group in groups)
{
Console.Write($"{group.Key}: ");
Console.WriteLine(string.Join(", ", group));
} Option Strict On
Imports System
Imports System.Linq
Module GroupByDemo
Sub Main()
Dim words As String() = {"cat", "car", "bat", "bee", "cab"}
Dim groups = words.GroupBy(Function(word) word(0))
For Each wordGroup In groups
Console.Write($"{wordGroup.Key}: ")
Console.WriteLine(String.Join(", ", wordGroup))
Next
End Sub
End Module GroupBy() works identically in both languages. The lambda uses Function(word) word(0) in Visual Basic — note the parenthesis character access (word(0) not word[0]). Each group is an IGrouping(Of TKey, TElement) with a .Key property. The group itself is enumerable, so String.Join() can iterate it directly.LINQ aggregates
var scores = new[] { 85, 92, 78, 96, 88 };
Console.WriteLine(scores.Count());
Console.WriteLine(scores.Sum());
Console.WriteLine(scores.Average());
Console.WriteLine(scores.Min());
Console.WriteLine(scores.Max());
Console.WriteLine(scores.Any(score => score >= 95));
Console.WriteLine(scores.All(score => score >= 70)); Option Strict On
Imports System
Imports System.Linq
Module AggregatesDemo
Sub Main()
Dim scores As Integer() = {85, 92, 78, 96, 88}
Console.WriteLine(scores.Count())
Console.WriteLine(scores.Sum())
Console.WriteLine(scores.Average())
Console.WriteLine(scores.Min())
Console.WriteLine(scores.Max())
Console.WriteLine(scores.Any(Function(score) score >= 95))
Console.WriteLine(scores.All(Function(score) score >= 70))
End Sub
End Module All standard LINQ aggregate methods —
Count(), Sum(), Average(), Min(), Max(), Any(), All(), First(), FirstOrDefault() — work identically in Visual Basic. The predicate lambdas use Function(x) condition syntax. The return types are the same as in C# — Average() on integers returns Double.Async / Await
Async Function
async Task<string> FetchGreetingAsync(string name)
{
await Task.Delay(0);
return $"Hello, {name}!";
}
string greeting = await FetchGreetingAsync("Alice");
Console.WriteLine(greeting); Option Strict On
Imports System
Imports System.Threading.Tasks
Module AsyncDemo
Async Function FetchGreetingAsync(name As String) As Task(Of String)
Await Task.Delay(0)
Return $"Hello, {name}!"
End Function
Async Function RunAsync() As Task
Dim greeting As String = Await FetchGreetingAsync("Alice")
Console.WriteLine(greeting)
End Function
Sub Main()
RunAsync().GetAwaiter().GetResult()
End Sub
End Module Visual Basic uses
Async Function ... As Task(Of T) where C# uses async Task<T>. The Await keyword is capitalized but otherwise identical to C#'s lowercase await. A key difference from C#: Visual Basic does not support Async Sub Main() or Async Function Main() As Task as an entry point — the compiler rejects them with BC36934. The idiom is a synchronous Sub Main() that calls RunAsync().GetAwaiter().GetResult() to block until the async work finishes.Task.WhenAll — parallel tasks
async Task<int> SlowComputeAsync(int value)
{
await Task.Delay(100);
return value * value;
}
int[] results = await Task.WhenAll(
SlowComputeAsync(2),
SlowComputeAsync(3),
SlowComputeAsync(4)
);
Console.WriteLine(string.Join(", ", results)); Option Strict On
Imports System
Imports System.Threading.Tasks
Module TaskWhenAllDemo
Async Function SlowComputeAsync(value As Integer) As Task(Of Integer)
Await Task.Delay(100)
Return value * value
End Function
Async Function RunAsync() As Task
Dim results As Integer() = Await Task.WhenAll(
SlowComputeAsync(2),
SlowComputeAsync(3),
SlowComputeAsync(4)
)
Console.WriteLine(String.Join(", ", results))
End Function
Sub Main()
RunAsync().GetAwaiter().GetResult()
End Sub
End Module Task.WhenAll() awaits multiple tasks in parallel and collects their results into an array — identical behavior in both languages. The generic form Task(Of Integer()) in Visual Basic corresponds to Task<int[]> in C#. Visual Basic's implicit line continuation (after the open parenthesis) allows the multi-line call without backslash continuation characters.⚠ Gotchas for C# Developers
⚠ And / Or are NOT logical operators
int x = 0;
// && short-circuits: 10 / x is never reached when x == 0
if (x != 0 && 10 / x > 1)
Console.WriteLine("safe");
Console.WriteLine("No crash — && protected against division by zero");
// & on booleans does NOT short-circuit (both sides always evaluated)
bool evaluated = (x != 0) & (x > -1);
Console.WriteLine($"& result: {evaluated}"); Option Strict On
Imports System
Module AndOrGotchaDemo
Sub Main()
Dim x As Integer = 0
' SAFE: AndAlso short-circuits — 10 \ x is never evaluated
If x <> 0 AndAlso 10 \ x > 1 Then
Console.WriteLine("safe")
End If
' DANGER: And does NOT short-circuit — throws DivideByZeroException!
' If x <> 0 And 10 \ x > 1 Then ...
Console.WriteLine("AndAlso protected us")
End Sub
End Module This is the single most dangerous gotcha when coming from C#. In Visual Basic,
And and Or are bitwise / non-short-circuit operators — they always evaluate both sides. AndAlso and OrElse are the short-circuit logical operators equivalent to C#'s && and ||. Always use AndAlso/OrElse in boolean conditions — bugs from accidentally using And/Or can be extremely subtle.⚠ No ++ / -- operators
int count = 0;
count++; // increment
count--; // decrement
count += 5; // add-assign (works in both)
Console.WriteLine(count); Option Strict On
Imports System
Module IncrementGotchaDemo
Sub Main()
Dim count As Integer = 0
count += 1 ' no ++ operator in Visual Basic
count -= 1 ' no -- operator in Visual Basic
count += 5 ' add-assign works the same
Console.WriteLine(count)
End Sub
End Module Visual Basic has no
++ or -- operators. Use += 1 and -= 1 instead. The compound assignment operators (+=, -=, *=, /=, \=, &=) all work the same as in C#. This also means there is no prefix/postfix distinction — no equivalent of x++ vs ++x returning different values.⚠ Dim arr(n) means n+1 elements
// C#: new int[5] creates 5 elements, indices 0-4
int[] numbers = new int[5];
Console.WriteLine(numbers.Length); // 5
int[] initialized = { 10, 20, 30 };
Console.WriteLine(initialized[2]); // 30 Option Strict On
Imports System
Module ArrayBoundGotchaDemo
Sub Main()
' Dim arr(4) creates 5 elements — index 0 through 4
' The argument is the UPPER BOUND, not the length!
Dim numbers(4) As Integer
Console.WriteLine(numbers.Length) ' 5
' Array literal: no size — length determined by elements
Dim initialized As Integer() = {10, 20, 30}
Console.WriteLine(initialized(2)) ' 30
End Sub
End Module When declaring an array with
Dim arr(n), n is the upper bound (the highest valid index), not the length. Dim numbers(4) creates 5 elements with indices 0–4. In C#, new int[5] creates 5 elements with the same indices. This off-by-one difference is a frequent source of bugs when translating between the two languages. Array literals (As Type() = {...}) are unaffected — the length is determined by the elements.⚠ Visual Basic is case-insensitive
// C# is CASE-SENSITIVE
// These are three different identifiers:
int Count = 0;
// int count = 0; // would be a different variable
// int COUNT = 0; // also different
Console.WriteLine(Count); Option Strict On
Imports System
Module CaseInsensitiveDemo
Sub Main()
' Visual Basic is case-INSENSITIVE for identifiers and keywords
Dim count As Integer = 42
' These all refer to the same variable:
Console.WriteLine(Count) ' same as count
Console.WriteLine(COUNT) ' same as count
Console.WriteLine(count) ' same as count
' Keywords can be any case: DIM, Dim, dim are all valid
DIM message AS STRING = "hello"
console.writeline(message)
End Sub
End Module Visual Basic is case-insensitive for both identifiers and keywords —
count, Count, and COUNT all refer to the same variable. C# is strictly case-sensitive. This means you cannot use case alone to distinguish a type from a variable (Person person is legal in C# but ambiguous in Visual Basic). By convention, Visual Basic code uses PascalCase for all public members and camelCase for local variables, but the compiler enforces nothing.