PONY λ M2 Modula-2

C#.CodeCompared.To/Visual Basic

An interactive executable cheatsheet comparing C# and Visual Basic

C# 13 Visual Basic (.NET 9)
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&lt;T&gt; 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&lt;K,V&gt; 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.