Variables, Types & Operators
Variable Declaration & Built-in Types
VB.NET is statically typed with Dim declarations. Common types: String, Integer (32-bit), Long (64-bit), Double (64-bit float), Decimal (high-precision for financial), Boolean, Date, Char. Use the type suffix (D for Decimal, F for Single, L for Long) to force literal types. Option Explicit On forces variable declaration (prevents typos). Option Infer On enables type inference with Dim without explicit types. Nullable types (Integer?) wrap value types so they can hold Nothing — useful for databases and APIs.
' Option Explicit On -- requires declaration (recommended)
Module Variables
Sub Main()
Dim name As String = "Alice" ' text
Dim age As Integer = 30 ' 32-bit integer
Dim salary As Decimal = 50000.50D ' precise money
Dim pi As Double = 3.14159 ' 64-bit float
Dim isDev As Boolean = True ' True/False
Dim letter As Char = "A"c ' single character
Dim today As Date = #2024-06-18# ' date literal
' type inference (Option Infer On)
Dim count = 10 ' inferred as Integer
Dim message = "Hello" ' inferred as String
' nullable types (Value types that can be Nothing)
Dim score As Integer? = Nothing
If score.HasValue Then Console.WriteLine(score.Value)
' varType() returns the Type object
Console.WriteLine(name.GetType().Name) ' String
End Sub
End ModuleConstants, Enums & Structures
Const declares compile-time constants (implicitly Shared, can't change). Enum defines named integer constants — use [Enum].Parse to convert strings and CInt to get the numeric value. Bracket notation [Error] escapes reserved keywords. The <Flags> attribute marks an enum as a bit field, allowing combination with Or/And operators — common for permissions and options. Always specify underlying type (As Integer) for flags to control bit width.
' constants (compile-time, implicitly Shared)
Public Const Pi As Double = 3.14159265358979
Public Const MaxRetries As Integer = 3
' enum: named integer constants
Public Enum LogLevel
Debug = 0
Info = 1
Warning = 2
[Error] = 3 ' brackets for reserved words
End Enum
Module EnumsDemo
Sub Main()
Dim level As LogLevel = LogLevel.Warning
Console.WriteLine(level) ' Warning
Console.WriteLine(CInt(level)) ' 2
' parse string to enum
Dim parsed = [Enum].Parse(GetType(LogLevel), "Info")
Console.WriteLine(parsed) ' Info
' Flags attribute for bit-field enums
Dim perms As FilePermission = FilePermission.Read Or FilePermission.Write
If (perms And FilePermission.Read) <> 0 Then
Console.WriteLine("Has read")
End If
End Sub
End Module
<Flags>
Public Enum FilePermission As Integer
None = 0
Read = 1
Write = 2
Execute = 4
End EnumType Conversion & Casting
Widening conversions (Integer to Double) are implicit and safe. Narrowing conversions (Double to Integer) require explicit casting. CInt/CStr/CDate/CDec are VB's conversion functions (CType is the generic version). DirectCast is strictest — only works if the runtime type matches exactly. TryCast returns Nothing on failure instead of throwing (reference types only). Always prefer TryParse over Parse for user input to avoid exceptions. IsNumeric/IsDate are handy validation helpers. CInt rounds (banker's rounding) while Int() floors.
' widening conversions (implicit, safe)
Dim i As Integer = 42
Dim d As Double = i ' Integer -> Double (no data loss)
' narrowing conversions (explicit, may lose data)
Dim d2 As Double = 3.99
Dim i2 As Integer = CInt(d2) ' 4 (rounds, not truncates)
Dim i3 As Integer = Int(d2) ' 3 (floor)
Dim s As String = CStr(42) ' "42"
Dim n As Integer = CInt("100") ' 100 (throws if invalid)
' DirectCast vs CType vs TryCast
Dim obj As Object = "Hello"
Dim s2 As String = DirectCast(obj, String) ' strict, same type only
Dim s3 As String = CType(obj, String) ' flexible, converts
' TryCast: returns Nothing if cast fails (reference types only)
Dim s4 As String = TryCast(obj, String)
If s4 IsNot Nothing Then Console.WriteLine(s4)
' conversion functions
Dim b As Boolean = CBool(1) ' True
Dim dt As Date = CDate("2024-06-18")
Dim dec As Decimal = CDec("99.99")
' IsNumeric, IsDate checks
If IsNumeric("42") Then Console.WriteLine("is number")
If IsDate("2024-06-18") Then Console.WriteLine("is date")
' Parse vs TryParse (safer)
Dim num As Integer
If Integer.TryParse("123", num) Then
Console.WriteLine(num) ' 123
End IfOperators & Expressions
VB uses \ for integer division and / for real division (unlike C# which uses / and %). Mod is the remainder operator. VB uses = for both assignment and equality comparison (context determines which). And/Or evaluate both operands; AndAlso/OrElse short-circuit (preferred for performance and avoiding null references). & is the string concatenation operator (not +, which can do numeric addition). Use AndAlso/OrElse by default to short-circuit and avoid errors like checking Nothing objects.
' arithmetic
Dim a As Integer = 10
Console.WriteLine(a + 3) ' 13
Console.WriteLine(a - 4) ' 6
Console.WriteLine(a * 2) ' 20
Console.WriteLine(a / 3) ' 3.333 (Double, real division)
Console.WriteLine(a \ 3) ' 3 (integer division)
Console.WriteLine(a Mod 3) ' 1 (remainder)
Console.WriteLine(a ^ 2) ' 100 (exponent)
' comparison (return Boolean)
Console.WriteLine(5 > 3) ' True
Console.WriteLine(5 = 5) ' True (VB uses = for equality)
Console.WriteLine(5 <> 4) ' True (not equal)
Console.WriteLine("a" < "b") ' True (string comparison)
' logical operators
Dim x As Boolean = True
Dim y As Boolean = False
Console.WriteLine(x And y) ' False (evaluates both)
Console.WriteLine(x Or y) ' True
Console.WriteLine(Not x) ' False
Console.WriteLine(x Xor y) ' True (exclusive or)
Console.WriteLine(x AndAlso y) ' False (short-circuit)
Console.WriteLine(x OrElse y) ' True (short-circuit)
' bitwise (on integers)
Console.WriteLine(5 And 3) ' 1 (0101 And 0011 = 0001)
Console.WriteLine(5 Or 3) ' 7
Console.WriteLine(5 Xor 3) ' 6
' string concatenation
Dim s As String = "Hello" & " " & "World" ' & operator
Dim s2 As String = $"Hello {a}" ' interpolationInput, Output & Formatting
Console.WriteLine/Write are the basic output methods. String interpolation ($"...") is the modern, readable way to embed expressions — supports format specifiers like :F2 (2 decimals), :C (currency), :P (percent), :X (hex). String.Format uses positional placeholders {0}, {1} with optional alignment ({0,-10} = left-aligned, width 10). For loops that build large strings, use StringBuilder (not &) to avoid creating many intermediate strings. Integer.Parse throws on invalid input; use TryParse for safety.
Module IO
Sub Main()
' console output
Console.WriteLine("Hello, World!") ' with newline
Console.Write("No newline") ' without newline
Console.WriteLine() ' blank line
' string interpolation ($ before the string)
Dim name As String = "Alice"
Dim age As Integer = 30
Console.WriteLine($"Hello, {name}. You are {age} years old.")
Console.WriteLine($"Next year: {age + 1}") ' expressions work
Console.WriteLine($"Pi: {3.14159:F2}") ' format specifier
' String.Format (positional)
Dim msg As String = String.Format("{0} is {1} years old", name, age)
' composite formatting with alignment
Console.WriteLine("{0,-10} {1,5}", "Alice", 30) ' left/right align
Console.WriteLine("{0:C}", 1234.56) ' currency: $1,234.56
Console.WriteLine("{0:P}", 0.25) ' percent: 25.00 %
Console.WriteLine("{0:X}", 255) ' hex: FF
' console input
Console.Write("Enter name: ")
Dim input As String = Console.ReadLine()
Console.Write("Enter age: ")
Dim ageInput As Integer = Integer.Parse(Console.ReadLine())
' StringBuilder for efficient concatenation
Dim sb As New System.Text.StringBuilder()
For i As Integer = 1 To 100
sb.Append($"Line {i}").AppendLine()
Next
Console.WriteLine(sb.ToString())
End Sub
End ModuleControl Flow
If...Then...Else & Ternary
If...Then...ElseIf...Else is VB's conditional. AndAlso/OrElse short-circuit (don't evaluate the right side if unnecessary) — always prefer these over And/Or for conditions. The If() function (VB 14+) is the ternary operator: If(condition, trueVal, falseVal). If() with two args is the null-coalescing operator: If(maybeNull, defaultValue). Avoid the old IIf() function — it always evaluates both branches and returns Object (boxing). Single-line If doesn't need End If.
Dim score As Integer = 85
' multi-line If
If score >= 90 Then
Console.WriteLine("A")
ElseIf score >= 80 Then
Console.WriteLine("B")
ElseIf score >= 70 Then
Console.WriteLine("C")
Else
Console.WriteLine("F")
End If
' single-line If (no End If)
If score > 50 Then Console.WriteLine("Pass")
' nested If with AndAlso (short-circuit)
Dim age As Integer = 25
Dim hasLicense As Boolean = True
If age >= 18 AndAlso hasLicense Then
Console.WriteLine("Can drive")
End If
' If as expression (VB 14+ ternary)
Dim grade As String = If(score >= 60, "Pass", "Fail")
' nullable coalescing
Dim name As String = Nothing
Dim displayName As String = If(name, "Unknown") ' "Unknown"
' IIf function (old-style, always evaluates both sides)
Dim result As String = IIf(score > 50, "Pass", "Fail")Select Case (Switch)
Select Case is VB's switch statement — cleaner than chained If...ElseIf. Case supports multiple values (comma-separated), ranges (To), and comparison operators (Is >= 90). Case Else is the default branch. Unlike C# switch, VB doesn't fall through — each Case runs independently and no Break is needed. Select Case works with strings, numbers, and even objects (using Is). It's ideal for dispatching on discrete values.
Dim grade As String = "B"
' basic Select Case
Select Case grade
Case "A"
Console.WriteLine("Excellent")
Case "B", "C" ' multiple values
Console.WriteLine("Good")
Case "D" To "F" ' range
Console.WriteLine("Needs improvement")
Case Else
Console.WriteLine("Invalid grade")
End Select
' numeric ranges
Dim score As Integer = 85
Select Case score
Case Is >= 90 ' comparison operator
Console.WriteLine("A")
Case 80 To 89 ' range
Console.WriteLine("B")
Case 70 To 79
Console.WriteLine("C")
Case Is < 70
Console.WriteLine("F")
End Select
' Case with expressions
Dim day As Integer = 3
Select Case day
Case 1, 2, 3, 4, 5
Console.WriteLine("Weekday")
Case 6, 7
Console.WriteLine("Weekend")
End SelectFor, For Each & Iterators
For...To...Next iterates a range with an optional Step (negative for countdown). For Each iterates any IEnumerable — cleaner for collections. Exit For breaks the loop; Continue For skips to the next iteration. Iterator functions (with Yield) produce sequences lazily — values are generated on demand, which is memory-efficient for large or infinite sequences. Yield returns one value, then resumes where it left off on the next iteration. This is VB's equivalent of C# yield return.
' For loop with step
For i As Integer = 0 To 4
Console.WriteLine(i) ' 0 1 2 3 4
Next
For i As Integer = 10 To 1 Step -2
Console.WriteLine(i) ' 10 8 6 4 2
Next
' nested loops
For row As Integer = 1 To 3
For col As Integer = 1 To 3
Console.Write($"{row * col} ")
Next
Console.WriteLine()
Next
' For Each (iterates collections)
Dim fruits() As String = {"apple", "banana", "cherry"}
For Each fruit As String In fruits
Console.WriteLine(fruit)
Next
' For Each with List
Dim nums As New List(Of Integer) From {1, 2, 3}
For Each n As Integer In nums
Console.WriteLine(n)
Next
' Exit For / Continue For
For i As Integer = 1 To 100
If i > 5 Then Exit For ' break
If i Mod 2 = 0 Then Continue For ' skip even
Console.WriteLine(i) ' 1 3 5
Next
' Iterator function (yields one item at a time)
Iterator Function Evens(max As Integer) As IEnumerable(Of Integer)
For i As Integer = 2 To max Step 2
Yield i
Next
End Function
For Each e In Evens(10)
Console.WriteLine(e) ' 2 4 6 8 10
NextDo While, Do Until & While
Do While runs while the condition is True; Do Until runs while the condition is False (until it becomes True). Placing the condition at the bottom (Loop While/Until) guarantees the body runs at least once — useful for menus and input validation. While...End While is the older, simpler form. Exit Do breaks; Continue Do skips to the next iteration. Choose Do While when you might not enter the loop; Do...Loop While when you must run at least once.
' Do While: test at top (may never run)
Dim count As Integer = 0
Do While count < 3
Console.WriteLine(count) ' 0 1 2
count += 1
Loop
' Do Until: runs UNTIL condition is true
count = 0
Do Until count >= 3
Console.WriteLine(count) ' 0 1 2
count += 1
Loop
' Do...Loop While: test at bottom (runs at least once)
count = 0
Do
Console.WriteLine(count)
count += 1
Loop While count < 3
' Do...Loop Until: test at bottom, runs until true
count = 0
Do
Console.WriteLine(count)
count += 1
Loop Until count >= 3
' While...End While (older syntax)
Dim i As Integer = 0
While i < 3
Console.WriteLine(i)
i += 1
End While
' Exit Do / Continue Do
Dim n As Integer = 0
Do While n < 100
n += 1
If n Mod 2 = 0 Then Continue Do ' skip even
If n > 10 Then Exit Do ' break
Console.WriteLine(n) ' 1 3 5 7 9
LoopWith...End With & GoTo
With...End With is syntactic sugar for accessing multiple members of one object without repeating the variable name — useful for initialization and builders. GoTo jumps to a label; modern VB discourages it except for breaking out of deeply nested loops or in legacy On Error GoTo error handling. On Error GoTo is VB6-style error handling (Resume Next skips the erroring line) — prefer structured Try/Catch in new code. The Err object holds the last error's description and number.
' With...End With: access members of one object repeatedly
Dim sb As New System.Text.StringBuilder()
With sb
.Append("Hello")
.Append(", ")
.Append("World")
.AppendLine()
End With
Console.WriteLine(sb.ToString())
' With on anonymous types
Dim person = New With {.Name = "Alice", .Age = 30}
With person
Console.WriteLine($"{.Name} is {.Age}")
End With
' nested With (use caution — confusing)
With New System.Drawing.Point(3, 4)
Console.WriteLine($"X={.X}, Y={.Y}")
End With
' GoTo (use sparingly — only for error handling or deep breaks)
For i As Integer = 1 To 3
For j As Integer = 1 To 3
If i + j > 4 Then GoTo done ' break out of both loops
Console.WriteLine($"{i},{j}")
Next
Next
done:
Console.WriteLine("Finished")
' On Error GoTo (legacy error handling — prefer Try/Catch)
On Error GoTo errorHandler
Dim result As Integer = 10 \ 0
Exit Sub
errorHandler:
Console.WriteLine("Error: " & Err.Description)
Resume NextProcedures, Functions & Events
Sub & Function Basics
Sub performs an action (no return value); Function returns a value. Return exits the function with a value. The old-style 'assign to function name' (Multiply = a * b) still works but Return is clearer. When calling a Sub, parentheses are optional (Greet "Alice" or Greet("Alice")). Functions called for their return value require parentheses. By default, VB lets you call functions without assigning the return value, but this can be confusing — be explicit.
' Sub: performs an action, returns nothing
Sub Greet(name As String)
Console.WriteLine($"Hello, {name}!")
End Sub
' Function: returns a value
Function Add(a As Integer, b As Integer) As Integer
Return a + b ' explicit return
End Function
' Function without Return: use function name
Function Multiply(a As Integer, b As Integer) As Integer
Multiply = a * b ' old-style: assign to function name
End Function
' calling
Greet("Alice")
Dim sum As Integer = Add(3, 4)
Console.WriteLine(sum) ' 7
' parentheses are optional for Subs with no return value
Greet "Alice" ' valid (no parens)
' passing arguments by position
Console.WriteLine(Add(1, 2))
' Sub with multiple statements
Sub PrintReport(title As String, lines As Integer)
Console.WriteLine(New String("-"c, 40))
Console.WriteLine(title)
Console.WriteLine(New String("-"c, 40))
For i As Integer = 1 To lines
Console.WriteLine($"Line {i}")
Next
End Sub
PrintReport("Monthly Report", 5)Parameters: ByVal, ByRef, Optional, ParamArray
ByVal (default) passes a copy — changes inside don't affect the caller. ByRef passes a reference — changes DO affect the caller (like ref in C#). Use ByRef when a procedure must modify the caller's variable or return multiple values. Optional parameters have defaults and must come after required ones. ParamArray accepts a variable number of arguments (like params in C#) and must be the last parameter. Named arguments (name:=value) improve readability and allow skipping optional parameters.
' ByVal (default): pass by value (copy)
Sub ResetValue(ByVal x As Integer)
x = 0 ' only changes the copy
End Sub
' ByRef: pass by reference (can modify caller's variable)
Sub Increment(ByRef x As Integer)
x += 1 ' modifies the original
End Sub
Dim n As Integer = 5
ResetValue(n)
Console.WriteLine(n) ' 5 (unchanged)
Increment(n)
Console.WriteLine(n) ' 6 (modified)
' Optional parameters (must have default values, must be last)
Function Power(base As Double, Optional exp As Double = 2) As Double
Return Math.Pow(base, exp)
End Function
Console.WriteLine(Power(3)) ' 9 (exp defaults to 2)
Console.WriteLine(Power(2, 10)) ' 1024
' ParamArray: variable number of arguments (must be last)
Function Sum(ParamArray nums() As Integer) As Integer
Dim total As Integer = 0
For Each n In nums
total += n
Next
Return total
End Function
Console.WriteLine(Sum(1, 2, 3)) ' 6
Console.WriteLine(Sum(1, 2, 3, 4, 5)) ' 15
Console.WriteLine(Sum()) ' 0
' named arguments (improve readability)
Sub CreateAccount(name As String, age As Integer, active As Boolean)
' ...
End Sub
CreateAccount(name:="Bob", active:=True, age:=25)Lambda Expressions & Delegates
Lambdas are inline functions: Function(...) for ones that return a value, Sub(...) for ones that don't. Func(Of T, TResult) is a built-in delegate type for functions; Action(Of T) is for subs (no return). AddressOf creates a delegate from a named method. Lambdas are essential for LINQ (Where, Select, OrderBy take function arguments). Multi-line lambdas use Function...End Function (or Sub...End Sub). Delegates are type-safe function pointers — useful for callbacks, events, and strategy patterns.
' single-line lambda (Function returns a value)
Dim square As Func(Of Integer, Integer) = Function(x) x * x
Console.WriteLine(square(5)) ' 25
' multi-line lambda
Dim factorial As Func(Of Integer, Integer) = Function(n)
Dim result As Integer = 1
For i As Integer = 2 To n
result *= i
Next
Return result
End Function
Console.WriteLine(factorial(5)) ' 120
' Sub lambda (no return value)
Dim log As Action(Of String) = Sub(msg) Console.WriteLine($"[LOG] {msg}")
log("Hello")
' lambda with multiple parameters
Dim add As Func(Of Integer, Integer, Integer) = Function(a, b) a + b
Console.WriteLine(add(3, 4)) ' 7
' use lambdas with LINQ
Dim nums = {1, 2, 3, 4, 5}
Dim evens = nums.Where(Function(n) n Mod 2 = 0).ToList()
Dim doubled = nums.Select(Function(n) n * 2).ToList()
Dim total = nums.Sum(Function(n) n)
' delegate definition
Delegate Function MathOp(a As Integer, b As Integer) As Integer
' assign methods to delegates
Dim op As MathOp
op = AddressOf AddNumbers ' AddNumbers is a regular Function
Console.WriteLine(op(3, 4)) ' 7
' Action and Func (built-in delegate types)
Dim print As Action(Of String) = AddressOf Console.WriteLine
print("Hello")Events & Event Handlers
Events enable the observer pattern: a class raises events, and subscribers handle them. Declare with Event, fire with RaiseEvent, handle with Handles (WithEvents, compile-time) or AddHandler (runtime, dynamic). WithEvents + Handles is declarative but the variable must be module/class-level. AddHandler/RemoveHandler allow subscribing/unsubscribing at runtime — useful for dynamic wiring. Event handlers must match the event's signature. Events are the backbone of Windows Forms and WPF (button clicks, form load, etc.).
' define an event
Public Class TemperatureSensor
Public Event TemperatureChanged(temp As Double)
Private _current As Double
Public Property Current As Double
Get
Return _current
End Get
Set(value As Double)
If value <> _current Then
_current = value
RaiseEvent TemperatureChanged(value) ' fire the event
End If
End Set
End Property
End Class
' subscribe to an event
Module SensorDemo
WithEvents sensor As New TemperatureSensor()
Sub Main()
sensor.Current = 20.5 ' triggers handler
sensor.Current = 21.0 ' triggers handler
End Sub
' event handler (must match the event signature)
Sub OnTempChanged(temp As Double) Handles sensor.TemperatureChanged
Console.WriteLine($"Temperature changed to {temp:F1}°C")
End Sub
End Module
' alternative: AddHandler (dynamic, runtime)
Module DynamicDemo
Sub Main()
Dim s As New TemperatureSensor()
AddHandler s.TemperatureChanged, AddressOf HandleTemp
s.Current = 25.0
RemoveHandler s.TemperatureChanged, AddressOf HandleTemp
End Sub
Sub HandleTemp(temp As Double)
Console.WriteLine($"Got: {temp}")
End Sub
End ModuleOverloading, Recursion & Shared Members
Overloading lets multiple methods share a name with different parameter lists — the compiler picks the best match. Overloads keyword is required when overloading across inheritance. Recursion is when a function calls itself — needs a base case to terminate. Watch for stack overflow with deep recursion. Shared (C# static) members belong to the type, not instances — call them via ClassName.Member without creating an object. Shared constructors run once per type, ideal for initializing static data.
' overloading: same name, different parameters
Class Calculator
Overloads Function Add(a As Integer, b As Integer) As Integer
Return a + b
End Function
Overloads Function Add(a As Double, b As Double) As Double
Return a + b
End Function
Overloads Function Add(values() As Integer) As Integer
Dim total As Integer = 0
For Each v In values
total += v
Next
Return total
End Function
End Class
' recursion: function calls itself
Function Factorial(n As Integer) As Long
If n <= 1 Then Return 1
Return n * Factorial(n - 1)
End Function
Console.WriteLine(Factorial(5)) ' 120
' Fibonacci (inefficient — for demo)
Function Fib(n As Integer) As Integer
If n < 2 Then Return n
Return Fib(n - 1) + Fib(n - 2)
End Function
' Shared (static) members: belong to the class, not instances
Class MathHelper
Public Shared ReadOnly Pi As Double = 3.14159265358979
Public Shared Function CircleArea(radius As Double) As Double
Return Pi * radius * radius
End Function
End Class
' call without creating an instance
Console.WriteLine(MathHelper.Pi)
Console.WriteLine(MathHelper.CircleArea(5))
' Shared constructor (runs once, before any access)
Class Config
Shared Sub New()
Console.WriteLine("Config initialized")
End Sub
End ClassStrings & Text Processing
String Basics & Immutability
Strings in VB.NET are immutable — every method (ToUpper, Replace, etc.) returns a NEW string; the original is unchanged. Use = for equality comparison (VB-specific). IndexOf returns the first index of a substring (-1 if not found). Split breaks a string into an array; Join combines an array into a string. Trim removes whitespace (or specified chars) from both ends. For case-insensitive comparison, use StringComparison.OrdinalIgnoreCase. For heavy string manipulation in loops, use StringBuilder.
Dim s As String = "Hello, World"
' properties
Console.WriteLine(s.Length) ' 12
Console.WriteLine(s(0)) ' H (indexer, read-only)
' comparison
Console.WriteLine("abc" = "abc") ' True (= is equality in VB)
Console.WriteLine("abc".Equals("abc")) ' True
Console.WriteLine(String.Equals("a", "A", StringComparison.OrdinalIgnoreCase)) ' True
' case conversion (returns new string — strings are immutable)
Console.WriteLine(s.ToUpper()) ' HELLO, WORLD
Console.WriteLine(s.ToLower()) ' hello, world
' searching
Console.WriteLine(s.IndexOf("o")) ' 4 (first occurrence)
Console.WriteLine(s.LastIndexOf("o")) ' 8
Console.WriteLine(s.Contains("World")) ' True
Console.WriteLine(s.StartsWith("Hello")) ' True
Console.WriteLine(s.EndsWith("World")) ' True
' substring
Console.WriteLine(s.Substring(7)) ' World
Console.WriteLine(s.Substring(0, 5)) ' Hello
' split and join
Dim parts() As String = "a,b,c,d".Split(","c)
Console.WriteLine(String.Join("-", parts)) ' a-b-c-d
' replace and trim
Console.WriteLine(s.Replace("o", "0")) ' Hell0, W0rld
Console.WriteLine(" hi ".Trim()) ' hi
Console.WriteLine(" hi ".TrimStart()) ' hi<spaces>
Console.WriteLine("xxhelloxx".Trim("x"c)) ' helloStringBuilder & Performance
String is immutable — every & or += creates a new string, copying all previous content. In loops, this is O(n²). StringBuilder is mutable — Append/Insert/Remove modify the same buffer, making it O(n). Always use StringBuilder for loops that build large strings. StringBuilder is chainable (Append returns the same instance). Set the initial Capacity if you know the approximate size to avoid reallocations. For one-off concatenations (not in loops), & is fine and more readable.
Imports System.Text
' StringBuilder: mutable string for efficient concatenation
Dim sb As New StringBuilder()
For i As Integer = 1 To 1000
sb.Append("Line ").Append(i).AppendLine() ' chainable
Next
Dim result As String = sb.ToString()
' StringBuilder vs & concatenation
' BAD: creates 1000 intermediate strings
Dim bad As String = ""
For i As Integer = 1 To 1000
bad &= $"Line {i}" ' creates new string each time
Next
' GOOD: StringBuilder mutates in place
Dim good As New StringBuilder()
For i As Integer = 1 To 1000
good.AppendLine($"Line {i}")
Next
' useful StringBuilder methods
Dim sb2 As New StringBuilder("Hello")
sb2.Append(", World") ' append string
sb2.AppendLine() ' append newline
sb2.AppendFormat("Count: {0}", 42) ' formatted append
sb2.Insert(0, ">> ") ' insert at position
sb2.Remove(0, 3) ' remove 3 chars at position 0
sb2.Replace("World", "VB") ' replace substring
Console.WriteLine(sb2.ToString())
Console.WriteLine(sb2.Length)
' pre-allocate capacity for known sizes
Dim sb3 As New StringBuilder(10000) ' initial capacityString Interpolation & Formatting
String interpolation ($"...") is the modern way to embed variables and expressions — readable and supports format specifiers after a colon (:C currency, :F2 fixed decimals, :N thousands separators, :X hex, :D5 zero-padded, :P percent). Date formats: yyyy-MM-dd, HH:mm:ss, dddd (full day name). Use String.Format for positional placeholders or when the format string is built dynamically. For culture-aware apps (internationalization), pass a CultureInfo to ToString/Format to control decimal separators, currency symbols, and date formats.
Dim name As String = "Alice"
Dim age As Integer = 30
Dim salary As Decimal = 50000.5D
Dim today As Date = Date.Now
' interpolated string ($ prefix)
Console.WriteLine($"Name: {name}, Age: {age}")
Console.WriteLine($"Next year: {age + 1}") ' expressions
Console.WriteLine($"Upper: {name.ToUpper()}") ' method calls
' format specifiers in interpolation
Console.WriteLine($"Salary: {salary:C}") ' $50,000.50
Console.WriteLine($"Salary: {salary:N2}") ' 50,000.50
Console.WriteLine($"Pi: {Math.PI:F4}") ' 3.1416
Console.WriteLine($"Date: {today:yyyy-MM-dd}") ' 2024-06-18
Console.WriteLine($"Time: {today:HH:mm:ss}") ' 14:30:00
Console.WriteLine($"Hex: {255:X}") ' FF
Console.WriteLine($"Padded: {42:D5}") ' 00042
Console.WriteLine($"Percent: {0.25:P0}") ' 25%
' alignment in interpolation
Console.WriteLine($"{"Name",-10} | {"Age",5}") ' left/right align
Console.WriteLine($"{name,-10} | {age,5}")
' String.Format (positional, older style)
Dim msg As String = String.Format("{0} is {1} years old", name, age)
' composite formatting with format string
Console.WriteLine(String.Format("Price: {0:C2}", 19.99))
' custom date formats
Console.WriteLine(today.ToString("dddd, MMMM d, yyyy"))
Console.WriteLine(today.ToString("yyyy/MM/dd HH:mm"))
' culture-specific formatting
Dim ci As Globalization.CultureInfo = New Globalization.CultureInfo("fr-FR")
Console.WriteLine(salary.ToString("C", ci)) ' 50 000,50 €Regular Expressions
Regex.IsMatch tests for a match; Regex.Matches finds all; Regex.Match finds the first. Groups capture parts of the match with parentheses — access via Groups(1), Groups(2). Regex.Replace substitutes matches (use $1, $2 to reference groups). RegexOptions.Compiled compiles the pattern for faster repeated matching. Common patterns: \d (digit), \w (word char), \s (whitespace), + (one or more), * (zero or more), {n} (exactly n), ^/$ (start/end). Always validate user input with regex for emails, phones, etc.
Imports System.Text.RegularExpressions
' basic matching
Dim pattern As String = "d+" ' one or more digits
Dim input As String = "abc 123 def 456"
If Regex.IsMatch(input, pattern) Then
Console.WriteLine("Contains digits")
End If
' extract matches
For Each m As Match In Regex.Matches(input, "d+")
Console.WriteLine(m.Value) ' 123, 456
Next
' single match with groups
Dim m2 As Match = Regex.Match("2024-06-18", "(d{4})-(d{2})-(d{2})")
If m2.Success Then
Console.WriteLine(m2.Groups(1).Value) ' 2024 (year)
Console.WriteLine(m2.Groups(2).Value) ' 06 (month)
Console.WriteLine(m2.Groups(3).Value) ' 18 (day)
End If
' replace
Dim cleaned As String = Regex.Replace("phone: 123-456-7890", "D", "")
Console.WriteLine(cleaned) ' 1234567890 (digits only)
' replace with match reference ($1, $2)
Dim swapped As String = Regex.Replace("John Doe", "(w+) (w+)", "$2, $1")
Console.WriteLine(swapped) ' Doe, John
' split
Dim parts() As String = Regex.Split("a,b;;c", "[,;]+")
' common patterns
Dim emailPattern As String = "^[w.-]+@[w.-]+.w+$"
Dim urlPattern As String = "^https?://[w./-]+$"
Dim phonePattern As String = "^d{3}-d{3}-d{4}$"
' compiled regex (faster for repeated use)
Dim rx As New Regex("d+", RegexOptions.Compiled)
Dim count As Integer = rx.Matches(input).CountChar Operations & Encoding
Char is a single Unicode character (2 bytes). Use the c suffix for char literals ("A"c). Char methods (IsDigit, IsLetter, IsUpper) are useful for validation. Asc/Chr convert between chars and ASCII codes. ToCharArray converts a string to a mutable char array (strings themselves are immutable). Encoding.UTF8.GetBytes converts strings to byte arrays (essential for file I/O and networking). Base64 (Convert.ToBase64String) encodes binary data as text — used in data URIs, email attachments, and APIs.
' Char basics
Dim c As Char = "A"c ' char literal (c suffix)
Console.WriteLine(Char.IsDigit("5"c)) ' True
Console.WriteLine(Char.IsLetter("A"c)) ' True
Console.WriteLine(Char.IsWhiteSpace(" "c)) ' True
Console.WriteLine(Char.ToUpper("a"c)) ' A
Console.WriteLine(Char.ToLower("A"c)) ' a
' convert between char and ASCII code
Dim code As Integer = Asc("A"c) ' 65
Dim ch As Char = Chr(66) ' B
' iterate characters in a string
Dim s As String = "Hello"
For Each c In s
Console.Write($"{Asc(c)} ") ' 72 101 108 108 111
Next
' char array <-> string
Dim chars() As Char = s.ToCharArray()
Array.Reverse(chars)
Dim reversed As String = New String(chars)
Console.WriteLine(reversed) ' olleH
' encoding: string <-> bytes
Dim text As String = "Hello"
Dim bytes() As Byte = System.Text.Encoding.UTF8.GetBytes(text)
Console.WriteLine(bytes.Length) ' 5
Dim decoded As String = System.Text.Encoding.UTF8.GetString(bytes)
' ASCII encoding (1 byte per char, no Unicode)
Dim asciiBytes() As Byte = System.Text.Encoding.ASCII.GetBytes(text)
' Base64 encoding (for binary data in text)
Dim b64 As String = Convert.ToBase64String(bytes)
Dim original() As Byte = Convert.FromBase64String(b64)
' StringBuilder from char array
Dim sb As New System.Text.StringBuilder(New String(chars))Arrays & Collections
Arrays: Declaration, Indexing & Methods
Arrays in VB are 0-indexed and fixed-size. Dim arr(n) creates n+1 elements (0 to n). Array literals use { }. Multi-dimensional arrays (,) are rectangular; jagged arrays ()() are arrays of arrays (each row can have different length). ReDim resizes an array — Preserve keeps existing values (without it, values are cleared). Array.Sort/Reverse/Clear/IndexOf/Copy are static helpers. Use List(Of T) instead of arrays when the size changes frequently.
' fixed-size array
Dim nums(4) As Integer ' 5 elements (0-4), default 0
nums(0) = 10
nums(1) = 20
' array literal
Dim fruits() As String = {"apple", "banana", "cherry"}
Dim nums2() As Integer = {1, 2, 3, 4, 5}
' type inference
Dim mixed = {1, 2, 3} ' Integer()
Dim names = {"Alice", "Bob"} ' String()
' indexing (0-based)
Console.WriteLine(fruits(0)) ' apple
Console.WriteLine(fruits.Length) ' 3
Console.WriteLine(nums2.GetUpperBound(0)) ' 4 (last index)
' multi-dimensional arrays
Dim grid(2, 2) As Integer ' 3x3
grid(0, 0) = 1
grid(1, 2) = 5
' jagged arrays (array of arrays)
Dim jagged()() As Integer = {
New Integer() {1, 2},
New Integer() {3, 4, 5},
New Integer() {6}
}
Console.WriteLine(jagged(1)(2)) ' 5
' Array class methods
Array.Sort(nums2) ' sort in place
Array.Reverse(nums2) ' reverse
Array.Clear(nums2, 0, 2) ' set first 2 to 0
Dim found As Integer = Array.IndexOf(nums2, 3) ' search
Dim copy(4) As Integer
Array.Copy(nums2, copy, 5) ' copy
' resize (creates new array)
ReDim Preserve nums2(9) ' resize, keep existing values
' ReDim without Preserve clears values
' iterate
For Each n As Integer In nums2
Console.WriteLine(n)
NextList(Of T): Dynamic Arrays
List(Of T) is the go-to dynamic collection — it grows automatically as you Add. Count gives the number of elements (not capacity). Add/Insert/Remove/RemoveAt/Contains/IndexOf are the core methods. Find/FindAll/Exists take a predicate (lambda) for custom searches. Sort can take a Comparison delegate for custom ordering. List(Of T) wraps an array internally and resizes it when full (amortized O(1) Add). Convert to array with ToArray() when you need a fixed-size collection.
Imports System.Collections.Generic
' create and initialize
Dim nums As New List(Of Integer) From {1, 2, 3}
Dim names As New List(Of String)
' add and insert
nums.Add(4) ' add to end
nums.AddRange({5, 6, 7}) ' add multiple
nums.Insert(0, 0) ' insert at index
Console.WriteLine(nums.Count) ' 8
' access and modify
Console.WriteLine(nums(0)) ' 0 (indexer)
nums(0) = 100 ' modify
' remove
nums.Remove(3) ' remove by value (first match)
nums.RemoveAt(0) ' remove by index
nums.RemoveRange(0, 2) ' remove 2 starting at index 0
nums.Clear() ' remove all
' search
nums = New List(Of Integer) From {1, 2, 3, 4, 5}
Console.WriteLine(nums.Contains(3)) ' True
Console.WriteLine(nums.IndexOf(3)) ' 2
Dim found As Boolean = nums.Exists(Function(n) n > 3)
Dim first As Integer = nums.Find(Function(n) n > 3) ' 4
Dim all As List(Of Integer) = nums.FindAll(Function(n) n > 2) ' 3,4,5
' iterate
For Each n As Integer In nums
Console.WriteLine(n)
Next
' convert to array
Dim arr() As Integer = nums.ToArray()
' sort and reverse
nums.Sort()
nums.Reverse()
' List with custom objects
Dim people As New List(Of (Name As String, Age As Integer))
people.Add(("Alice", 30))
people.Add(("Bob", 25))
people.Sort(Function(a, b) a.Age.CompareTo(b.Age)) ' sort by ageDictionary, HashSet & Queue/Stack
Dictionary(Of TKey, TValue) maps keys to values — O(1) lookup by key. Use TryGetValue to avoid exceptions and double lookups (ContainsKey + indexer). Iterate with KeyValuePair. HashSet(Of T) stores unique elements with O(1) add/contains — use for deduplication and set operations (UnionWith, IntersectWith, ExceptWith). Queue is FIFO (Enqueue/Dequeue); Stack is LIFO (Push/Pop). All these collections are generic (type-safe, no boxing). Choose based on access pattern: lookup → Dictionary, uniqueness → HashSet, order → Queue/Stack.
Imports System.Collections.Generic
' Dictionary: key-value pairs
Dim ages As New Dictionary(Of String, Integer) From {
{"Alice", 30},
{"Bob", 25}
}
ages("Eve") = 28 ' add or update
ages.Add("Carol", 22) ' add (throws if key exists)
' access
Console.WriteLine(ages("Alice")) ' 30
Console.WriteLine(ages.ContainsKey("Bob")) ' True
Console.WriteLine(ages.ContainsValue(28)) ' True
' safe access (TryGetValue avoids double lookup)
Dim age As Integer
If ages.TryGetValue("Alice", age) Then
Console.WriteLine(age) ' 30
End If
' iterate
For Each kv As KeyValuePair(Of String, Integer) In ages
Console.WriteLine($"{kv.Key}: {kv.Value}")
Next
' remove
ages.Remove("Bob")
' HashSet: unique elements
Dim unique As New HashSet(Of Integer) From {1, 2, 3}
unique.Add(2) ' no effect (already exists)
unique.Add(4)
Console.WriteLine(unique.Count) ' 4
' set operations
Dim setA As New HashSet(Of Integer) From {1, 2, 3}
Dim setB As New HashSet(Of Integer) From {3, 4, 5}
setA.UnionWith(setB) ' {1,2,3,4,5}
setA.IntersectWith(setB) ' {3}
setA.ExceptWith(setB) ' remove setB from setA
' Queue: FIFO
Dim q As New Queue(Of String)
q.Enqueue("first")
q.Enqueue("second")
Console.WriteLine(q.Dequeue()) ' first
Console.WriteLine(q.Peek()) ' second (without removing)
' Stack: LIFO
Dim stk As New Stack(Of Integer)
stk.Push(1)
stk.Push(2)
Console.WriteLine(stk.Pop()) ' 2
Console.WriteLine(stk.Peek()) ' 1LINQ: Query & Method Syntax
LINQ (Language Integrated Query) transforms collections declaratively. Query syntax (From...Where...Select) is SQL-like and readable for complex queries. Method syntax (.Where().Select()) is fluent and chains well. Both compile to the same code. Key operators: Where (filter), Select (transform), OrderBy (sort), GroupBy (group), Take/Skip (paginate), First/FirstOrDefault (find), Any/All (test), Sum/Average/Max/Min (aggregate). FirstOrDefault returns the default (0, Nothing) if no match — avoids exceptions. LINQ works on any IEnumerable (arrays, lists, dictionaries).
Imports System.Linq
Dim nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
Dim people = {
New With {.Name = "Alice", .Age = 30, .City = "NYC"},
New With {.Name = "Bob", .Age = 25, .City = "LA"},
New With {.Name = "Carol", .Age = 35, .City = "NYC"}
}
' query syntax (SQL-like)
Dim adults = From p In people
Where p.Age >= 30
Select p.Name
' Alice, Carol
Dim nycNames = From p In people
Where p.City = "NYC"
Order By p.Age
Select p.Name, p.Age
' method syntax (fluent, with lambdas)
Dim evens = nums.Where(Function(n) n Mod 2 = 0).ToList() ' 2,4,6,8,10
Dim doubled = nums.Select(Function(n) n * 2).ToList()
Dim sum = nums.Sum() ' 55
Dim avg = nums.Average() ' 5.5
Dim max = nums.Max()
Dim count = nums.Count(Function(n) n > 5) ' 5
' ordering
Dim sorted = nums.OrderBy(Function(n) n).ToList()
Dim descSorted = nums.OrderByDescending(Function(n) n).ToList()
' grouping
Dim byCity = people.GroupBy(Function(p) p.City)
For Each g In byCity
Console.WriteLine($"{g.Key}: {g.Count()} people")
Next
' take / skip (pagination)
Dim first3 = nums.Take(3).ToList() ' 1,2,3
Dim skip3 = nums.Skip(3).Take(3).ToList() ' 4,5,6
' first / single / elementAt
Dim first = nums.First() ' 1
Dim firstEven = nums.First(Function(n) n Mod 2 = 0) ' 2
Dim maybeFirst = nums.FirstOrDefault(Function(n) n > 100) ' 0 (default)
' any / all
Dim hasEven = nums.Any(Function(n) n Mod 2 = 0) ' True
Dim allPositive = nums.All(Function(n) n > 0) ' True
' aggregate
Dim product = nums.Aggregate(Function(acc, n) acc * n) ' factorial-likeCollection Initialization & Tuples
Collection initializers (From { }) create and populate collections in one statement. Tuples (VB 2017+) group multiple values — named fields (X, Y) are clearer than Item1, Item2. Tuples are perfect for returning multiple values from a function without defining a class. Deconstruction (Dim (a, b) = tuple) splits a tuple into variables. ToDictionary converts a list to a dictionary (key selector + value selector). Tuples are value types (structs), so they're efficient for small, temporary groupings.
Imports System.Collections.Generic
' collection initializers (From clause)
Dim nums As New List(Of Integer) From {1, 2, 3, 4, 5}
Dim dict As New Dictionary(Of String, Integer) From {
{"one", 1},
{"two", 2},
{"three", 3}
}
Dim set_ As New HashSet(Of String) From {"a", "b", "c"}
' tuples (VB 2017+)
Dim point As (X As Integer, Y As Integer) = (X := 3, Y := 4)
Console.WriteLine(point.X) ' 3
Console.WriteLine(point.Y) ' 4
' tuple without named fields (Item1, Item2, ...)
Dim t As (String, Integer) = ("Alice", 30)
Console.WriteLine(t.Item1) ' Alice
Console.WriteLine(t.Item2) ' 30
' tuple as function return (multiple values)
Function GetStats(nums As List(Of Integer)) As (Count As Integer, Sum As Integer, Avg As Double)
Return (
nums.Count,
nums.Sum(),
nums.Average()
)
End Function
Dim stats = GetStats(nums)
Console.WriteLine($"Count={stats.Count}, Sum={stats.Sum}, Avg={stats.Avg:F2}")
' deconstruct tuples
Dim (name, age) = ("Bob", 25)
Console.WriteLine($"{name}, {age}")
' list of tuples
Dim people As New List(Of (Name As String, Age As Integer)) From {
("Alice", 30),
("Bob", 25)
}
' iterate with index
For i As Integer = 0 To nums.Count - 1
Console.WriteLine($"{i}: {nums(i)}")
Next
' LINQ to dictionary
Dim nameToAge = people.ToDictionary(Function(p) p.Name, Function(p) p.Age)
Console.WriteLine(nameToAge("Alice")) ' 30Object-Oriented Programming
Class, Fields & Properties
Classes bundle data (fields/properties) and behavior (methods). Auto-implemented properties (Property X As Type) auto-generate a hidden backing field — concise for simple data. Full properties (Get/Set blocks) allow validation, computation, or side effects. ReadOnly properties have only a Get. Me refers to the current instance (like this in C#). Constructors (Sub New) initialize objects. Properties look like fields to callers (p.Name) but execute code — this encapsulation is OOP's core benefit.
Public Class Person
' private field (backing store)
Private _name As String
Private _age As Integer
' auto-implemented property (compiler creates the field)
Public Property Email As String
' property with custom logic
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
If String.IsNullOrEmpty(value) Then
Throw New ArgumentException("Name required")
End If
_name = value
End Set
End Property
' property with validation
Public Property Age As Integer
Get
Return _age
End Get
Set(value As Integer)
If value < 0 OrElse value > 150 Then
Throw New ArgumentOutOfRangeException("Age")
End If
_age = value
End Set
End Property
' read-only property (computed)
Public ReadOnly Property IsAdult As Boolean
Get
Return _age >= 18
End Get
End Property
' constructor
Public Sub New(name As String, age As Integer)
Me.Name = name ' Me refers to the current instance
Me.Age = age
End Sub
End Class
' usage
Dim p As New Person("Alice", 30)
Console.WriteLine(p.Name) ' Alice
Console.WriteLine(p.IsAdult) ' True
p.Age = 31 ' uses setter
' p.Age = 200 ' throws (validation)Inheritance & MyBase
Inherits establishes an IS-A relationship (Dog IS an Animal). MyBase.New() calls the base constructor (must be the first statement). Overridable (base) + Overrides (derived) enable polymorphism — the derived method runs even when accessed via a base reference. Shadows (or Overloads) hides a base member rather than overriding (no polymorphism). Use Overridable/Overrides for true polymorphism; Shadows only when you must hide a member you can't change. VB supports single inheritance (one base class) but multiple interfaces.
' base class
Public Class Animal
Public Property Name As String
Public Sub New(name As String)
Me.Name = name
End Sub
Public Overridable Function Speak() As String
Return Name & " makes a sound"
End Function
Public Overridable Sub Eat()
Console.WriteLine(Name & " is eating")
End Sub
End Class
' derived class (Inherits)
Public Class Dog
Inherits Animal
Public Property Breed As String
' call base constructor with MyBase
Public Sub New(name As String, breed As String)
MyBase.New(name) ' must be first line
Me.Breed = breed
End Sub
' override virtual method
Public Overrides Function Speak() As String
Return Name & " says Woof"
End Function
' extend base method
Public Overrides Sub Eat()
MyBase.Eat() ' call base implementation
Console.WriteLine(Name & " wags tail")
End Sub
' new method (not override — hides base)
Public Shadows Sub Description()
Console.WriteLine($"{Name} ({Breed})")
End Sub
End Class
Dim d As New Dog("Rex", "Labrador")
Console.WriteLine(d.Speak()) ' Rex says Woof
d.Eat() ' Rex is eating / Rex wags tail
' polymorphism: base reference, derived object
Dim a As Animal = New Dog("Buddy", "Poodle")
Console.WriteLine(a.Speak()) ' Buddy says Woof (virtual dispatch)Interfaces & Polymorphism
Interfaces define a contract (methods, properties) without implementation — classes Implement them. Unlike inheritance, a class can implement multiple interfaces. The Implements keyword links a member to its interface declaration (VB-specific syntax). Interfaces enable polymorphism: code can work with any IDrawable without knowing if it's a Circle or Square. TypeOf x Is T checks the runtime type; DirectCast casts (throws if invalid). Use interfaces to decouple code: depend on IDrawable, not Circle. This is the Dependency Inversion principle.
' interface: contract (no implementation)
Public Interface IComparable(Of T)
Function CompareTo(other As T) As Integer
End Interface
Public Interface IDrawable
Sub Draw()
Property X As Integer
Property Y As Integer
End Interface
' implement an interface
Public Class Circle
Implements IDrawable
Public Property X As Integer Implements IDrawable.X
Public Property Y As Integer Implements IDrawable.Y
Public Property Radius As Integer
Public Sub Draw() Implements IDrawable.Draw
Console.WriteLine($"Drawing circle at ({X}, {Y}) r={Radius}")
End Sub
End Class
Public Class Square
Implements IDrawable
Public Property X As Integer Implements IDrawable.X
Public Property Y As Integer Implements IDrawable.Y
Public Property Side As Integer
Public Sub Draw() Implements IDrawable.Draw
Console.WriteLine($"Drawing square at ({X}, {Y}) side={Side}")
End Sub
End Class
' polymorphism via interface
Dim shapes As New List(Of IDrawable) From {
New Circle With {.X = 0, .Y = 0, .Radius = 5},
New Square With {.X = 10, .Y = 10, .Side = 4}
}
For Each s In shapes
s.Draw() ' calls the right implementation
Next
' check and cast
Dim shape As IDrawable = New Circle()
If TypeOf shape Is Circle Then
Dim c As Circle = DirectCast(shape, Circle)
c.Radius = 10
End If
' multiple interfaces
Public Class TextBox
Implements IDrawable, IComparable(Of TextBox)
' ... implement both
End ClassGenerics & Constraints
Generics (Of T) let you write type-safe, reusable code that works with any type — no boxing, no casts, compile-time type checking. List(Of T), Dictionary(Of K,V), Stack(Of T) are generic collections. Constraints restrict the type parameter: Class (reference type), Structure (value type), New (has parameterless constructor — allows New T()), specific base class or interface. Generics avoid the performance hit of boxing (value types) and the fragility of Object casts. Always prefer generic collections (List(Of T)) over non-generic (ArrayList).
' generic class
Public Class Stack(Of T)
Private items As New List(Of T)
Public Sub Push(item As T)
items.Add(item)
End Sub
Public Function Pop() As T
If items.Count = 0 Then Throw New InvalidOperationException("Empty")
Dim last As T = items(items.Count - 1)
items.RemoveAt(items.Count - 1)
Return last
End Function
Public ReadOnly Property Count As Integer
Get
Return items.Count
End Get
End Property
End Class
' use with different types
Dim intStack As New Stack(Of Integer)
intStack.Push(1)
intStack.Push(2)
Console.WriteLine(intStack.Pop()) ' 2
Dim strStack As New Stack(Of String)
strStack.Push("Hello")
Console.WriteLine(strStack.Pop()) ' Hello
' generic method with constraint
Public Class Repository(Of T As {Class, New})
' T must be a reference type (Class) and have a parameterless constructor (New)
Private items As New List(Of T)
Public Sub Add(item As T)
items.Add(item)
End Sub
Public Function Find(match As Predicate(Of T)) As T
Return items.Find(match)
End Function
End Class
' generic function
Function FirstOrDefault(Of T)(list As IEnumerable(Of T), predicate As Func(Of T, Boolean)) As T
For Each item In list
If predicate(item) Then Return item
Next
Return Nothing ' default for reference types
End Function
' constraints:
' Class - reference type
' Structure - value type
' New - has parameterless constructor
' BaseClass - inherits from a specific class
' Interface - implements an interfacePartial Classes & Namespaces
Partial classes split a class across multiple files — the compiler merges them. Useful for separating generated code (designer files) from hand-written code, or splitting large classes. Namespaces organize types and prevent name collisions (MyApp.Models.User vs MyApp.Services.User). Imports brings namespace names into scope (no need for full qualification). The RootNamespace (project setting) wraps all types — set it to your company/product name. Conventions: namespaces match folder structure; one namespace per logical area.
' File1.vb
Namespace MyApp.Models
Partial Public Class User
Public Property Id As Integer
Public Property Name As String
Public Property Email As String
End Class
End Namespace
' File2.vb (same class, different file)
Namespace MyApp.Models
Partial Public Class User
Public Sub SendWelcomeEmail()
Console.WriteLine($"Welcome {Name}!")
End Sub
Public Function Validate() As Boolean
Return Not String.IsNullOrEmpty(Email)
End Function
End Class
End Namespace
' usage (combines both files)
Dim u As New MyApp.Models.User With {
.Id = 1,
.Name = "Alice",
.Email = "[email protected]"
}
u.SendWelcomeEmail()
Console.WriteLine(u.Validate()) ' True
' namespace nesting
Namespace MyApp.Services
Public Class UserService
Public Sub Create(name As String)
' ...
End Sub
End Class
End Namespace
' import to use short names
Imports MyApp.Models
Imports MyApp.Services
Module Program
Sub Main()
Dim user As New User() ' no full namespace needed
Dim svc As New UserService()
End Sub
End ModuleError Handling & Exceptions
Try...Catch...Finally
Try/Catch/Finally is structured exception handling. Catch blocks are checked in order — put specific exceptions first, Exception (base) last. Finally always runs (even on Return or uncaught exceptions) — use it for cleanup (closing files, releasing resources). Throw (bare) re-throws the current exception, preserving the stack trace. When filters catch conditionally (Catch...When condition). Exception properties: Message (description), StackTrace (call chain), Source (assembly). Never catch Exception silently without logging — that hides bugs.
Try
Dim x As Integer = 10
Dim y As Integer = 0
Dim z As Integer = x \ y ' integer division by zero
Catch ex As DivideByZeroException
Console.WriteLine("Cannot divide by zero: " & ex.Message)
Catch ex As OverflowException
Console.WriteLine("Number too large: " & ex.Message)
Catch ex As Exception ' catch-all (must be last)
Console.WriteLine("Unexpected error: " & ex.Message)
Finally
' always runs (even with Return or exception)
Console.WriteLine("Cleanup complete")
End Try
' nested Try
Try
Try
Dim arr() As Integer = {1, 2, 3}
Console.WriteLine(arr(10)) ' IndexOutOfRangeException
Catch ex As IndexOutOfRangeException
Console.WriteLine("Index error: " & ex.Message)
Throw ' re-throw to outer handler
End Try
Catch ex As Exception
Console.WriteLine("Outer caught: " & ex.Message)
End Try
' When filter (conditional catch)
Try
' risky code
Catch ex As Exception When ex.Message.Contains("network")
Console.WriteLine("Network error, retrying...")
End Try
' exception properties
Try
Dim n As Integer = Integer.Parse("abc")
Catch ex As FormatException
Console.WriteLine(ex.Message) ' human-readable
Console.WriteLine(ex.GetType().Name) ' FormatException
Console.WriteLine(ex.StackTrace) ' where it happened
Console.WriteLine(ex.Source) ' which assembly
End TryThrowing & Custom Exceptions
Throw New ExceptionType(...) raises an exception. Always pass a meaningful message and use NameOf() for parameter names (refactoring-safe). When wrapping exceptions, pass the original as innerException to preserve the cause. Custom exceptions should inherit from Exception (or a more specific base), be marked <Serializable>, and provide constructors (message, message+inner). Override ToString() for custom display. Custom exceptions let callers catch specific error types and handle them appropriately — use them for domain-specific errors (InvalidTransactionException, PaymentFailedException).
' throw a built-in exception
Sub CheckAge(age As Integer)
If age < 0 Then
Throw New ArgumentOutOfRangeException(NameOf(age), "Age cannot be negative")
ElseIf age > 150 Then
Throw New ArgumentOutOfRangeException(NameOf(age), "Age unrealistic")
End If
End Sub
' throw with inner exception (wrapping)
Function LoadConfig(path As String) As String
Try
Return File.ReadAllText(path)
Catch ex As FileNotFoundException
Throw New ConfigurationException("Config not found", ex) ' wrap
End Try
End Function
' custom exception (inherit from Exception)
<Serializable>
Public Class InvalidTransactionException
Inherits Exception
Public Property TransactionId As String
Public Sub New(message As String, transactionId As String)
MyBase.New(message)
Me.TransactionId = transactionId
End Sub
Public Sub New(message As String, transactionId As String, inner As Exception)
MyBase.New(message, inner)
Me.TransactionId = transactionId
End Sub
Public Overrides Function ToString() As String
Return $"Transaction {TransactionId} failed: {Message}"
End Function
End Class
' using the custom exception
Sub ProcessPayment(amount As Decimal, txnId As String)
If amount <= 0 Then
Throw New InvalidTransactionException("Amount must be positive", txnId)
End If
' ... process
End Sub
' catch custom exception
Try
ProcessPayment(-100, "TXN-001")
Catch ex As InvalidTransactionException
Console.WriteLine($"Failed: {ex.TransactionId} - {ex.Message}")
End TryUsing Statements & IDisposable
Using ensures Dispose() is called when the block exits — critical for resources (files, database connections, network streams) that aren't garbage-collected promptly. Multiple resources can be declared in one Using (comma-separated). Implement IDisposable when your class holds unmanaged resources or other IDisposable objects. The Dispose pattern: Dispose(disposing As Boolean) frees managed (when disposing=True) and unmanaged resources; GC.SuppressFinalize prevents the finalizer from running. Always wrap IDisposable objects in Using to prevent resource leaks.
Imports System.IO
' Using ensures Dispose is called (even on exception)
Using writer As New StreamWriter("output.txt")
writer.WriteLine("Hello")
writer.WriteLine("World")
End Using ' writer.Dispose() called automatically
' equivalent to:
Dim writer2 As StreamWriter = Nothing
Try
writer2 = New StreamWriter("output.txt")
writer2.WriteLine("Hello")
Finally
writer2?.Dispose()
End Using
' multiple resources in one Using
Using conn As New SqlClient.SqlConnection(connStr),
cmd As New SqlClient.SqlCommand("SELECT 1", conn)
conn.Open()
Dim result = cmd.ExecuteScalar()
End Using
' implement IDisposable for your class
Public Class FileManager
Implements IDisposable
Private stream As FileStream
Private disposed As Boolean = False
Public Sub New(path As String)
stream = New FileStream(path, FileMode.Open)
End Sub
Public Function ReadLine() As String
' ... read from stream
End Function
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposed Then
If disposing Then
' free managed resources
stream?.Dispose()
End If
' free unmanaged resources (if any)
disposed = True
End If
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class
' usage
Using fm As New FileManager("data.txt")
Dim line = fm.ReadLine()
End UsingException Best Practices
Exception best practices: (1) Don't catch exceptions you can't actually handle — let them propagate. (2) Never swallow exceptions silently (empty Catch) — at minimum log them. (3) Use TryParse instead of Parse for user input (exceptions are expensive and for truly exceptional cases). (4) Throw specific exception types (ArgumentOutOfRangeException, not Exception). (5) Use When filters to log without catching (return False to not catch). (6) Re-throw with Throw (bare) to preserve the stack trace — not Throw ex which resets it. Exceptions are for exceptional conditions, not normal control flow.
' 1. Don't catch exceptions you can't handle
' BAD: swallows all errors
Try
File.Delete(path)
Catch ex As Exception
' silent failure — bug hidden!
End Try
' GOOD: only catch what you expect
Try
File.Delete(path)
Catch ex As FileNotFoundException
' file already gone — that's fine
Catch ex As UnauthorizedAccessException
Console.WriteLine("Permission denied: " & ex.Message)
' re-throw or handle meaningfully
End Try
' 2. Log exceptions (don't swallow)
Try
ProcessData()
Catch ex As Exception
Logger.Error(ex, "ProcessData failed")
Throw ' re-throw after logging
End Try
' 3. Validate input (avoid exceptions for expected conditions)
' BAD: exception for control flow
Try
Dim n = Integer.Parse(input)
Catch ex As FormatException
n = 0
End Try
' GOOD: TryParse
Dim n As Integer
If Not Integer.TryParse(input, n) Then
n = 0
Console.WriteLine("Invalid number, using 0")
End If
' 4. Use specific exception types
If amount < 0 Then
Throw New ArgumentOutOfRangeException(NameOf(amount))
End If
' 5. Exception filters (When) for logging without catching
Try
RiskyOperation()
Catch ex As Exception When LogError(ex)
' only enters if LogError returns True
End Try
Function LogError(ex As Exception) As Boolean
Logger.Error(ex)
Return False ' don't catch, just log
End FunctionDebugging & Diagnostics
Debug.Assert/WriteLine only run in Debug builds (compiled out in Release) — use for invariants and diagnostics. Trace works in both builds — for production logging. #If DEBUG...#End If enables conditional compilation. Debugger.Break() acts like a programmatic breakpoint. Stopwatch measures elapsed time precisely (for benchmarking). StackTrace captures the call chain (useful for logging). EventLog writes to the Windows Event Log (requires admin to create the source). Use these tools to diagnose issues without modifying production behavior.
Imports System.Diagnostics
' Debug.Assert: only in Debug builds
Debug.Assert(age >= 0, "Age should be non-negative")
' Debug.WriteLine: only in Debug builds
Debug.WriteLine($"Processing {name}, age {age}")
' Trace: works in Release builds too
Trace.WriteLine("App started")
Trace.WriteLineIf(age > 100, "Unusual age")
' conditional compilation
#If DEBUG Then
Console.WriteLine("Debug build")
#Else
Console.WriteLine("Release build")
#End If
' Debugger.Break: pause in debugger (like a breakpoint)
If count > 1000 Then
Debugger.Break()
End If
' Stopwatch for performance measurement
Dim sw As Stopwatch = Stopwatch.StartNew()
For i As Integer = 1 To 1000000
' some work
Next
sw.Stop()
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms")
' StackTrace for debugging
Dim st As New StackTrace(True)
Console.WriteLine(st.ToString()) ' current call stack
' EventLog for Windows event logging (requires admin)
If Not EventLog.SourceExists("MyApp") Then
EventLog.CreateEventSource("MyApp", "Application")
End If
EventLog.WriteEntry("MyApp", "Started", EventLogEntryType.Information)
' process info
Dim p As Process = Process.GetCurrentProcess()
Console.WriteLine($"Memory: {p.WorkingSet64 \ 1024} KB")
Console.WriteLine($"PID: {p.Id}")File I/O & Streams
Text File Read & Write
File.WriteAllText/ReadAllText are simplest for small files. File.ReadAllLines returns a String array (loads entire file into memory). For large files, use StreamReader/StreamWriter with Using — they read/write line by line, keeping memory low. Using ensures the stream is closed even on exceptions. AppendAllText/AppendAllLines add to existing files. Always check File.Exists before reading to give a friendly error. For async I/O (non-blocking UI), use ReadAllTextAsync/WriteAllTextAsync with Await.
Imports System.IO
' write all text at once (simple)
File.WriteAllText("output.txt", "Hello, World!")
' append text
File.AppendAllText("log.txt", $"[{Date.Now}] Started{Environment.NewLine}")
' read all text
Dim content As String = File.ReadAllText("output.txt")
' read all lines into an array
Dim lines() As String = File.ReadAllLines("data.csv")
For Each line In lines
Console.WriteLine(line)
Next
' write all lines
File.WriteAllLines("nums.txt", {"one", "two", "three"})
' append lines
File.AppendAllLines("log.txt", {"line1", "line2"})
' stream-based writing (for large files)
Using writer As New StreamWriter("big.txt")
For i As Integer = 1 To 1000000
writer.WriteLine($"Line {i}")
Next
End Using ' flushes and closes automatically
' stream-based reading (line by line, memory-efficient)
Using reader As New StreamReader("big.txt")
Dim line As String
Do
line = reader.ReadLine()
If line Is Nothing Then Exit Do
Console.WriteLine(line)
Loop
End Using
' check if file exists
If File.Exists("data.txt") Then
Console.WriteLine("Found")
End If
' File.ReadAllLinesAsync (VB 15+ with async)
Async Function ReadAsync(path As String) As Task(Of String())
Return Await File.ReadAllLinesAsync(path)
End FunctionBinary Files & Serialization
BinaryWriter/Reader read/write primitive types in a compact binary format — read order must match write order. For structured data, prefer JSON (human-readable, language-agnostic). System.Text.Json (built-in, fast) or Newtonsoft.Json (popular, feature-rich) serialize objects to/from JSON. JsonSerializer.Serialize/Deserialize(Of T) are the core methods. Use JsonSerializerOptions for formatting (indented, camelCase). JSON is ideal for config files, APIs, and data exchange. Avoid BinaryFormatter (security risk — removed in .NET 5+).
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
' write binary data
Using fs As New FileStream("data.bin", FileMode.Create)
Using writer As New BinaryWriter(fs)
writer.Write(42) ' Integer
writer.Write(3.14) ' Double
writer.Write("Hello") ' String (length-prefixed)
writer.Write(True) ' Boolean
writer.Write(New Byte() {1, 2, 3}) ' byte array
End Using
End Using
' read binary data (must read in same order!)
Using fs As New FileStream("data.bin", FileMode.Open)
Using reader As New BinaryReader(fs)
Dim n As Integer = reader.ReadInt32()
Dim d As Double = reader.ReadDouble()
Dim s As String = reader.ReadString()
Dim b As Boolean = reader.ReadBoolean()
Dim bytes() As Byte = reader.ReadBytes(3)
Console.WriteLine($"{n}, {d}, {s}, {b}")
End Using
End Using
' JSON serialization (Newtonsoft.Json or System.Text.Json)
Imports System.Text.Json
Public Class User
Public Property Name As String
Public Property Age As Integer
End Class
Dim u As New User With {.Name = "Alice", .Age = 30}
Dim json As String = JsonSerializer.Serialize(u)
' {"Name":"Alice","Age":30}
File.WriteAllText("user.json", json)
Dim restored As User = JsonSerializer.Deserialize(Of User)(json)
' JSON with options
Dim opts As New JsonSerializerOptions With {
.WriteIndented = True,
.PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}
Dim prettyJson = JsonSerializer.Serialize(u, opts)Directory & Path Operations
Directory.CreateDirectory/GetFiles/GetDirectories/Exists/Delete manage folders. GetFiles supports search patterns (*.csv) and SearchOption.AllDirectories for recursion. The Path class handles paths safely across platforms — always use Path.Combine (not &) to join paths (handles separators). Path.GetTempFileName creates a unique temp file. FileInfo/DirectoryInfo are object-oriented wrappers over file/directory operations with properties (Length, CreationTime) and methods (CopyTo, MoveTo, Delete). Use these instead of string manipulation for paths.
Imports System.IO
' directory operations
Directory.CreateDirectory("backup/2024/june")
' list files
Dim files() As String = Directory.GetFiles("C:\temp")
Dim csvFiles() As String = Directory.GetFiles("C:\temp", "*.csv")
Dim allFiles() As String = Directory.GetFiles("C:\temp", "*.*", SearchOption.AllDirectories)
' list directories
Dim dirs() As String = Directory.GetDirectories("C:\temp")
' check and delete
If Directory.Exists("old") Then
Directory.Delete("old", recursive := True) ' delete with contents
End If
' Path class (cross-platform path handling)
Dim fullPath As String = Path.Combine("folder", "sub", "file.txt")
' folder\sub\file.txt (on Windows)
Console.WriteLine(Path.GetFileName("C:\temp\data.txt")) ' data.txt
Console.WriteLine(Path.GetExtension("photo.JPG")) ' .JPG
Console.WriteLine(Path.GetFileNameWithoutExtension("data.txt")) ' data
Console.WriteLine(Path.GetDirectoryName("C:\temp\data.txt")) ' C:\temp
Console.WriteLine(Path.GetFullPath("data.txt")) ' absolute path
' temp files
Dim tempFile As String = Path.GetTempFileName() ' creates a temp file
Dim tempDir As String = Path.GetTempPath()
Console.WriteLine(tempFile)
' file info
Dim fi As New FileInfo("data.txt")
If fi.Exists Then
Console.WriteLine($"Size: {fi.Length} bytes")
Console.WriteLine($"Created: {fi.CreationTime}")
Console.WriteLine($"Modified: {fi.LastWriteTime}")
Console.WriteLine($"Extension: {fi.Extension}")
fi.CopyTo("data_backup.txt", overwrite := True)
fi.MoveTo("renamed.txt")
fi.Delete()
End If
' directory info
Dim di As New DirectoryInfo("C:\temp")
For Each f As FileInfo In di.GetFiles("*.txt")
Console.WriteLine(f.Name)
NextCSV Parsing & Data Files
For simple CSV, Split(","c) works — but it breaks on quoted fields with commas ("Smith, John"). TextFieldParser (in Microsoft.VisualBasic.FileIO) handles quotes, escaped quotes, and delimiters correctly — use it for real CSV. It also parses fixed-width files (FieldWidths). For writing CSV, manually quote fields containing commas/quotes. My.Computer.FileSystem is a VB-specific convenience wrapper (simpler API, less control). For large CSVs, consider CsvHelper (NuGet) — a robust, streaming CSV library. Always handle the header row separately.
Imports System.IO
Imports Microsoft.VisualBasic.FileIO
' simple CSV split (doesn't handle quoted commas)
Dim lines() As String = File.ReadAllLines("data.csv")
For Each line In lines
Dim fields() As String = line.Split(","c)
Console.WriteLine($"{fields(0)}, {fields(1)}")
Next
' robust CSV with TextFieldParser (handles quotes, commas)
Using parser As New TextFieldParser("data.csv")
parser.TextFieldType = FieldType.Delimited
parser.Delimiters = New String() {","}
parser.HasFieldsEnclosedInQuotes = True
' skip header
If Not parser.EndOfData Then parser.ReadLine()
While Not parser.EndOfData
Dim fields() As String = parser.ReadFields()
Console.WriteLine($"Name: {fields(0)}, Age: {fields(1)}")
End While
End Using
' write CSV
Using writer As New StreamWriter("output.csv")
writer.WriteLine("Name,Age,City")
writer.WriteLine("Alice,30,NYC")
writer.WriteLine("Bob,25,LA")
' quote fields with commas
writer.WriteLine($"""Smith, John"",40,Chicago")
End Using
' parse fixed-width files
Using parser As New TextFieldParser("fixed.txt")
parser.TextFieldType = FieldType.FixedWidth
parser.FieldWidths = New Integer() {10, 5, 20}
End Using
' read with My.Computer.FileSystem (VB-specific shortcut)
Dim contents As String = My.Computer.FileSystem.ReadAllText("data.txt")
Dim allLines As Object = My.Computer.FileSystem.ReadAllBytes("data.bin")
' write bytes
File.WriteAllBytes("data.bin", New Byte() {1, 2, 3, 4, 5})Async File Operations
Async file I/O uses ReadToEndAsync/ReadLineAsync/WriteAsync with Await — the thread isn't blocked during I/O, improving responsiveness (especially in UI apps). Async methods return Task or Task(Of T); Await unwraps the result. For processing many files concurrently, start all tasks and Await Task.WhenAll (parallel I/O). Async Sub is for event handlers; Async Function for everything else. Async I/O shines in web servers (handling many requests) and desktop apps (keeping the UI responsive). The overhead is small, so use async for any I/O that might be slow.
Imports System.IO
Imports System.Threading.Tasks
' async read (non-blocking)
Async Function ReadFileAsync(path As String) As Task(Of String)
Using reader As New StreamReader(path)
Return Await reader.ReadToEndAsync()
End Using
End Function
' async write
Async Function WriteFileAsync(path As String, content As String) As Task
Using writer As New StreamWriter(path)
Await writer.WriteAsync(content)
End Using
End Function
' async line-by-line (memory-efficient for large files)
Async Function ProcessLargeFileAsync(path As String) As Task
Using reader As New StreamReader(path)
Dim line As String
Do
line = Await reader.ReadLineAsync()
If line Is Nothing Then Exit Do
' process line
Console.WriteLine(line)
Loop
End Using
End Function
' call async methods (must be in an Async Sub/Function)
Async Sub Main()
Dim content As String = Await ReadFileAsync("data.txt")
Console.WriteLine(content)
Await WriteFileAsync("output.txt", "Async write")
End Sub
' parallel file processing
Async Function ProcessFilesAsync(paths As String()) As Task
Dim tasks As New List(Of Task)
For Each path In paths
tasks.Add(ProcessFileAsync(path))
Next
Await Task.WhenAll(tasks) ' wait for all
End Function
Async Function ProcessFileAsync(path As String) As Task
Dim content = Await File.ReadAllTextAsync(path)
' process content
End Function
' File.ReadAllLinesAsync (.NET Core+)
Async Function CountLinesAsync(path As String) As Task(Of Integer)
Dim lines = Await File.ReadAllLinesAsync(path)
Return lines.Length
End FunctionWindows Forms & UI
Form Basics & Events
Windows Forms (WinForms) builds desktop UIs by adding Controls to a Form. Each control has properties (Text, Location, Size) and events (Click, Load, FormClosing). AddHandler wires events at runtime; Handles (with WithEvents) wires at compile-time. MessageBox.Show displays dialogs. OnLoad/OnFormClosing are overridable protected methods for form lifecycle. Application.Run starts the message loop. WinForms uses the designer (drag-and-drop in Visual Studio) — the generated code goes in a .Designer.vb partial class. For modern apps, consider WPF or WinUI.
Imports System.Windows.Forms
Imports System.Drawing
Public Class MainForm
Inherits Form
Public Sub New()
Me.Text = "My App" ' form title
Me.Size = New Size(400, 300)
Me.StartPosition = FormStartPosition.CenterScreen
' create a button
Dim btn As New Button With {
.Text = "Click Me",
.Location = New Point(150, 100),
.Size = New Size(100, 30)
}
AddHandler btn.Click, AddressOf OnButtonClick
Me.Controls.Add(btn)
' create a label
Dim lbl As New Label With {
.Text = "Hello",
.Location = New Point(150, 50),
.AutoSize = True
}
Me.Controls.Add(lbl)
End Sub
Private Sub OnButtonClick(sender As Object, e As EventArgs)
MessageBox.Show("Button clicked!", "Info",
MessageBoxButtons.OK, MessageBoxIcon.Information)
End Sub
' form events
Protected Overrides Sub OnLoad(e As EventArgs)
MyBase.OnLoad(e)
Console.WriteLine("Form loaded")
End Sub
Protected Overrides Sub OnFormClosing(e As FormClosingEventArgs)
Dim result = MessageBox.Show("Close?", "Confirm",
MessageBoxButtons.YesNo)
If result = DialogResult.No Then
e.Cancel = True
End If
MyBase.OnFormClosing(e)
End Sub
End Class
' run the app
Module Program
Sub Main()
Application.EnableVisualStyles()
Application.Run(New MainForm())
End Sub
End ModuleCommon Controls
Common WinForms controls: TextBox (text input, Multiline for area), CheckBox (boolean), RadioButton (mutually exclusive — group in GroupBox), ComboBox (dropdown), NumericUpDown (number input with spinner), ListBox (selectable list). Set Location (Point) and Size. Use object initializers (With {.Prop = value}) for concise setup. Controls.AddRange adds multiple controls at once. Each control has a default event (Button.Click, TextBox.TextChanged, ComboBox.SelectedIndexChanged) — double-click in the designer to generate a handler.
Imports System.Windows.Forms
Imports System.Drawing
Public Class ControlsForm
Inherits Form
Public Sub New()
' TextBox (single-line input)
Dim txt As New TextBox With {
.Location = New Point(20, 20),
.Width = 200
}
' multiline TextBox
Dim txtMulti As New TextBox With {
.Multiline = True,
.Location = New Point(20, 60),
.Size = New Size(200, 80),
.ScrollBars = ScrollBars.Vertical
}
' CheckBox
Dim chk As New CheckBox With {
.Text = "Enable feature",
.Location = New Point(20, 160),
.Checked = True
}
' RadioButton (group in GroupBox for mutual exclusion)
Dim grp As New GroupBox With {.Text = "Gender", .Location = New Point(20, 190), .Size = New Size(120, 80)}
Dim rb1 As New RadioButton With {.Text = "Male", .Location = New Point(10, 20), .Checked = True}
Dim rb2 As New RadioButton With {.Text = "Female", .Location = New Point(10, 40)}
grp.Controls.AddRange({rb1, rb2})
' ComboBox (dropdown)
Dim cmb As New ComboBox With {.Location = New Point(150, 190), .DropDownStyle = ComboBoxStyle.DropDownList}
cmb.Items.AddRange({"Red", "Green", "Blue"})
cmb.SelectedIndex = 0
' NumericUpDown
Dim num As New NumericUpDown With {
.Location = New Point(150, 220),
.Minimum = 0,
.Maximum = 100,
.Value = 50
}
' ListBox
Dim lst As New ListBox With {.Location = New Point(20, 280), .Size = New Size(150, 80)}
lst.Items.AddRange({"Apple", "Banana", "Cherry"})
Me.Controls.AddRange({txt, txtMulti, chk, grp, cmb, num, lst})
End Sub
End ClassLayout: Panels, Docking & Anchoring
WinForms layout: Dock (Top/Bottom/Left/Right/Fill) makes a control fill an edge — add Fill last so it takes remaining space. Anchor pins a control to parent edges (it resizes when the form resizes). TableLayoutPanel arranges controls in a grid (rows/columns with percent/absolute sizes) — best for forms. FlowLayoutPanel stacks controls in a flow (wraps automatically). Panel is a simple container for grouping. For responsive layouts, prefer TableLayoutPanel over manual positioning. The order of Controls.Add matters for docking (later adds overlap earlier).
Imports System.Windows.Forms
Imports System.Drawing
Public Class LayoutForm
Inherits Form
Public Sub New()
Me.Size = New Size(600, 400)
' Panel: container for grouping controls
Dim topPanel As New Panel With {
.Dock = DockStyle.Top, ' fill the top
.Height = 50,
.BackColor = Color.LightBlue
}
Dim bottomPanel As New Panel With {
.Dock = DockStyle.Bottom,
.Height = 40,
.BackColor = Color.LightGray
}
' Fill remaining space
Dim centerPanel As New Panel With {
.Dock = DockStyle.Fill,
.BackColor = Color.White
}
' Add in reverse Z-order (Fill last to actually fill)
Me.Controls.Add(centerPanel)
Me.Controls.Add(bottomPanel)
Me.Controls.Add(topPanel)
' Anchoring: control resizes with parent edges
Dim btn As New Button With {
.Text = "Anchored",
.Location = New Point(10, 10),
.Anchor = AnchorStyles.Top Or AnchorStyles.Left Or AnchorStyles.Right
}
centerPanel.Controls.Add(btn)
' TableLayoutPanel: grid layout
Dim tlp As New TableLayoutPanel With {
.Dock = DockStyle.Fill,
.ColumnCount = 2,
.RowCount = 2
}
tlp.ColumnStyles.Add(New ColumnStyle(SizeType.Percent, 50))
tlp.ColumnStyles.Add(New ColumnStyle(SizeType.Percent, 50))
tlp.Controls.Add(New Label With {.Text = "Name:"}, 0, 0)
tlp.Controls.Add(New TextBox(), 1, 0)
tlp.Controls.Add(New Label With {.Text = "Age:"}, 0, 1)
tlp.Controls.Add(New NumericUpDown(), 1, 1)
' FlowLayoutPanel: stacks controls left-to-right or top-to-bottom
Dim flp As New FlowLayoutPanel With {
.FlowDirection = FlowDirection.LeftToRight,
.Dock = DockStyle.Top,
.Height = 40
}
flp.Controls.AddRange({New Button With {.Text = "New"}, New Button With {.Text = "Open"}})
Me.Controls.Add(flp)
Me.Controls.Add(tlp)
End Sub
End ClassDialogs: OpenFileDialog, SaveFileDialog, ColorDialog
Common dialogs: OpenFileDialog (select file to open), SaveFileDialog (choose where to save), ColorDialog (pick a color), FontDialog (pick a font), FolderBrowserDialog (select a directory). Filter uses the format "Description|pattern|Description|pattern". ShowDialog returns DialogResult (OK/Cancel) — always check before using the result. Wrap dialogs in Using (they're IDisposable). OverwritePrompt prevents accidental overwrites. InitialDirectory starts the dialog in a useful location. These dialogs provide native Windows UI without custom code.
Imports System.Windows.Forms
Public Class DialogForm
Inherits Form
Private Sub OpenFile()
Using ofd As New OpenFileDialog
ofd.Title = "Select a file"
ofd.Filter = "Text files (*.txt)|*.txt|CSV files (*.csv)|*.csv|All files (*.*)|*.*"
ofd.FilterIndex = 1
ofd.Multiselect = False
ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
If ofd.ShowDialog() = DialogResult.OK Then
Dim path As String = ofd.FileName
MessageBox.Show($"Selected: {path}")
End If
End Using
End Sub
Private Sub SaveFile()
Using sfd As New SaveFileDialog
sfd.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
sfd.DefaultExt = "txt"
sfd.AddExtension = True
sfd.OverwritePrompt = True ' ask before overwriting
If sfd.ShowDialog() = DialogResult.OK Then
System.IO.File.WriteAllText(sfd.FileName, "Saved content")
End If
End Using
End Sub
Private Sub PickColor()
Using cd As New ColorDialog
cd.AllowFullOpen = True
cd.FullOpen = True
cd.Color = Color.Red
If cd.ShowDialog() = DialogResult.OK Then
Me.BackColor = cd.Color
End If
End Using
End Sub
Private Sub PickFont()
Using fd As New FontDialog
fd.ShowColor = True
fd.Font = Me.Font
If fd.ShowDialog() = DialogResult.OK Then
Me.Font = fd.Font
End If
End Using
End Sub
Private Sub PickFolder()
Using fbd As New FolderBrowserDialog
fbd.Description = "Select a folder"
fbd.SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
If fbd.ShowDialog() = DialogResult.OK Then
MessageBox.Show($"Folder: {fbd.SelectedPath}")
End If
End Using
End Sub
End ClassData Binding & DataGridView
Data binding connects UI controls to data objects automatically. Simple binding: txt.DataBindings.Add("Text", obj, "PropName"). For lists, set DataGridView.DataSource to a BindingList(Of T) or DataTable. INotifyPropertyChanged enables two-way binding — the UI updates when the property changes (raise PropertyChanged in the setter). BindingList(Of T) is like ObservableCollection — notifies the grid when items are added/removed. BindingSource adds navigation and filtering. AutoGenerateColumns creates columns from properties. Data binding eliminates manual sync code between UI and data.
Imports System.Windows.Forms
Imports System.ComponentModel
Public Class User
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private _name As String
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
If _name <> value Then
_name = value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(NameOf(Name)))
End If
End Set
End Property
Public Property Age As Integer
End Class
Public Class DataForm
Inherits Form
Public Sub New()
' simple binding: TextBox <-> object property
Dim user As New User With {.Name = "Alice", .Age = 30}
Dim txt As New TextBox With {.Location = New Point(20, 20)}
txt.DataBindings.Add("Text", user, "Name") ' two-way binding
' DataGridView with a list
Dim people As New BindingList(Of User) From {
New User With {.Name = "Alice", .Age = 30},
New User With {.Name = "Bob", .Age = 25}
}
Dim dgv As New DataGridView With {
.Location = New Point(20, 60),
.Size = New Size(350, 200),
.AutoGenerateColumns = True,
.AllowUserToAddRows = True,
.AllowUserToDeleteRows = True,
.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill
}
dgv.DataSource = people ' bind to list
' BindingSource for navigation
Dim bs As New BindingSource With {.DataSource = people}
dgv.DataSource = bs
' DataTable binding
Dim dt As New DataTable()
dt.Columns.Add("Name", GetType(String))
dt.Columns.Add("Age", GetType(Integer))
dt.Rows.Add("Carol", 28)
dt.Rows.Add("Dave", 35)
dgv.DataSource = dt
Me.Controls.Add(txt)
Me.Controls.Add(dgv)
End Sub
End ClassFile I/O Deep Dive
StreamReader & StreamWriter
StreamReader/StreamWriter handle text files with proper encoding and disposal. The Using block ensures files are closed even on exceptions — always use it. File.ReadAllLines/ReadAllText are convenient for small files; ReadLine in a loop is memory-efficient for large files. Async methods (ReadAllTextAsync) prevent UI freezing during I/O. StreamWriter with append:=True adds to existing files. Default encoding is UTF-8; specify explicitly with New StreamWriter(path, append, Encoding.UTF8) for legacy formats.
Imports System.IO
' write text file
Using writer As New StreamWriter("output.txt")
writer.WriteLine("First line")
writer.WriteLine("Second line")
writer.Write("No newline ")
writer.WriteLine("appended")
End Using ' automatically disposes/closes
' append to file
Using writer As New StreamWriter("log.txt", append:=True)
writer.WriteLine($"{Date.Now}: Application started")
End Using
' read all lines
Dim lines() As String = File.ReadAllLines("input.txt")
For Each line In lines
Console.WriteLine(line)
Next
' read line by line (memory efficient for big files)
Using reader As New StreamReader("big.txt")
Dim line As String
Do While reader.Peek() >= 0
line = reader.ReadLine()
' process line
Loop
End Using
' read all text at once
Dim content As String = File.ReadAllText("input.txt")
' async I/O (non-blocking)
Dim text As String = Await File.ReadAllTextAsync("input.txt")
Await File.WriteAllTextAsync("output.txt", "Hello")Binary & CSV File Handling
BinaryWriter/Reader store primitive types in a compact binary format — much smaller than text and faster to parse. Always read in the same order and types you wrote. For CSV, TextFieldParser handles quoted fields with embedded commas correctly (unlike naive Split). For production CSV, use a library like CsvHelper (NuGet) which handles edge cases, type mapping, and streaming. Binary files are platform-specific (endianness); use BinaryWriter with little-endian for cross-platform compatibility.
Imports System.IO
' binary file with BinaryWriter/Reader
Using fs As New FileStream("data.bin", FileMode.Create),
bw As New BinaryWriter(fs)
bw.Write(42) ' Integer
bw.Write(3.14) ' Double
bw.Write("Hello") ' String (length-prefixed)
bw.Write(True) ' Boolean
End Using
Using fs As New FileStream("data.bin", FileMode.Open),
br As New BinaryReader(fs)
Dim num As Integer = br.ReadInt32()
Dim pi As Double = br.ReadDouble()
Dim s As String = br.ReadString()
Dim flag As Boolean = br.ReadBoolean()
End Using
' CSV parsing (manual)
Dim csvLines = File.ReadAllLines("data.csv")
For Each line In csvLines.Skip(1) ' skip header
Dim fields = line.Split(","c)
Dim name = fields(0)
Dim age = Integer.Parse(fields(1))
Next
' CSV with TextFieldParser (handles quoted fields)
Using parser As New Microsoft.VisualBasic.FileIO.TextFieldParser("data.csv")
parser.TextFieldType = FileIO.FieldType.Delimited
parser.SetDelimiters(",")
While Not parser.EndOfData
Dim fields() As String = parser.ReadFields()
End While
End UsingFile System Operations
File and FileInfo provide file operations; Directory and DirectoryInfo handle folders. FileInfo/DirectoryInfo are more efficient when you need multiple attributes (one system call). EnumerateFiles is lazy (yields one at a time) vs GetFiles which loads all paths into memory — use Enumerate for large directories. Path.Combine builds paths with correct separators cross-platform; never concatenate strings manually. SearchOption.AllDirectories can throw on access-denied; catch UnauthorizedAccessException or use a recursive helper.
Imports System.IO
' file operations
File.Copy("source.txt", "dest.txt", overwrite:=True)
File.Move("old.txt", "new.txt")
File.Delete("unwanted.txt")
File.Exists("check.txt") ' Boolean
' file info
Dim fi As New FileInfo("data.txt")
Console.WriteLine($"Size: {fi.Length} bytes")
Console.WriteLine($"Created: {fi.CreationTime}")
Console.WriteLine($"Modified: {fi.LastWriteTime}")
Console.WriteLine($"Extension: {fi.Extension}")
' directory operations
Directory.CreateDirectory("path o
ewdir")
Directory.Exists("somedir")
Directory.Delete("dir", recursive:=True)
' enumerate files (lazy, memory-efficient)
For Each path In Directory.EnumerateFiles("C:data", "*.txt", SearchOption.AllDirectories)
Console.WriteLine(path)
Next
' directory info
Dim di As New DirectoryInfo("C:data")
For Each file In di.GetFiles("*.csv")
Console.WriteLine($"{file.Name} - {file.Length}")
Next
' path operations
Path.Combine("C:", "data", "file.txt") ' "C:dataile.txt"
Path.GetExtension("file.txt") ' ".txt"
Path.GetFileNameWithoutExtension("file.txt") ' "file"
Path.GetTempFileName() ' temp file pathSerialization (JSON & XML)
System.Text.Json (modern, fast) is preferred over the legacy DataContractJsonSerializer. JsonSerializer.Serialize/Deserialize handle most types; use JsonSerializerOptions for formatting and naming policies. For XML, XmlSerializer is simple but limited (no dictionaries, requires parameterless constructor). For complex scenarios (polymorphism, circular references), consider Newtonsoft.Json (Json.NET) via NuGet — more features but slower. Always handle deserialization errors (JsonException) for untrusted input.
Imports System.Text.Json
' define a class
Public Class Person
Public Property Name As String
Public Property Age As Integer
Public Property Email As String
End Class
' JSON serialize
Dim p As New Person With {.Name = "Alice", .Age = 30, .Email = "[email protected]"}
Dim json As String = JsonSerializer.Serialize(p)
File.WriteAllText("person.json", json)
' JSON with options
Dim opts As New JsonSerializerOptions With {
.WriteIndented = True,
.PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}
json = JsonSerializer.Serialize(p, opts)
' JSON deserialize
Dim text = File.ReadAllText("person.json")
Dim p2 = JsonSerializer.Deserialize(Of Person)(text)
' serialize a list
Dim people = New List(Of Person) From {p, p2}
json = JsonSerializer.Serialize(people)
' XML serialization
Imports System.Xml.Serialization
Dim xs As New XmlSerializer(GetType(Person))
Using fs As New FileStream("person.xml", FileMode.Create)
xs.Serialize(fs, p)
End Using
Using fs As New FileStream("person.xml", FileMode.Open)
Dim p3 = CType(xs.Deserialize(fs), Person)
End UsingAsync File Operations & Streams
Async file I/O keeps UIs responsive during large operations. ReadAsync/WriteAsync work with buffers for fine-grained progress reporting. IProgress(Of T) reports progress back to the UI thread safely. MemoryStream is for in-memory data (no disk). GZipStream compresses/decompresses streams on the fly. For network I/O, use HttpClient (async). Always use Using blocks to ensure streams are closed. Buffer size 81920 (80KB) is a good default — balances memory and syscall overhead.
Imports System.IO
' async copy with progress
Async Function CopyWithProgress(src As String, dest As String, progress As IProgress(Of Long)) As Task
Const bufferSize As Integer = 81920
Dim buffer(bufferSize - 1) As Byte
Using srcStream As FileStream = File.OpenRead(src),
destStream As FileStream = File.Create(dest)
Dim read As Integer
Dim total As Long = 0
Do
read = Await srcStream.ReadAsync(buffer, 0, bufferSize)
If read = 0 Then Exit Do
Await destStream.WriteAsync(buffer, 0, read)
total += read
progress?.Report(total)
Loop
End Using
End Function
' usage with progress bar
Dim prog As New Progress(Of Long)(Sub(bytes)
ProgressBar1.Value = CInt(bytes * 100 totalSize)
End Sub)
Await CopyWithProgress("big.zip", "copy.zip", prog)
' memory stream (in-memory)
Using ms As New MemoryStream()
Dim bw As New BinaryWriter(ms)
bw.Write("data")
ms.Position = 0
' read back
End Using
' GZip compression
Imports System.IO.Compression
Using src = File.OpenRead("data.txt"),
dest = File.Create("data.txt.gz"),
gz As New GZipStream(dest, CompressionMode.Compress)
src.CopyTo(gz)
End UsingADO.NET Database Access
Connections & Commands
Always use parameterized queries (Parameters.AddWithValue) to prevent SQL injection — never concatenate user input into SQL strings. SqlConnection, SqlCommand, and SqlDataReader are all IDisposable; wrap them in Using blocks. ExecuteReader returns rows; ExecuteNonQuery returns rows affected (INSERT/UPDATE/DELETE); ExecuteScalar returns a single value (COUNT, SUM). For other databases, use the appropriate provider (OracleConnection, OleDbConnection, OdbcConnection) or the generic DbProviderFactory.
Imports System.Data.SqlClient ' or Microsoft.Data.SqlClient
' connection string (SQL Server)
Dim connStr = "Server=localhost;Database=MyDb;Integrated Security=True;"
' basic query
Using conn As New SqlConnection(connStr)
conn.Open()
Using cmd As New SqlCommand("SELECT Id, Name FROM Users WHERE Age > @age", conn)
cmd.Parameters.AddWithValue("@age", 25)
Using reader As SqlDataReader = cmd.ExecuteReader()
While reader.Read()
Dim id = reader.GetInt32(0)
Dim name = reader.GetString(1)
Console.WriteLine($"{id}: {name}")
End While
End Using
End Using
End Using ' connection auto-closed
' insert with parameters (prevent SQL injection!)
Using conn As New SqlConnection(connStr), _
cmd As New SqlCommand("INSERT INTO Users (Name, Age) VALUES (@name, @age)", conn)
cmd.Parameters.AddWithValue("@name", "Bob")
cmd.Parameters.AddWithValue("@age", 30)
conn.Open()
Dim rowsAffected = cmd.ExecuteNonQuery()
End Using
' scalar query (single value)
Using conn As New SqlConnection(connStr), _
cmd As New SqlCommand("SELECT COUNT(*) FROM Users", conn)
conn.Open()
Dim count = CInt(cmd.ExecuteScalar())
End UsingDataSets & DataTables
DataTable/DataSet are disconnected data containers — load once, work offline, update later. SqlDataAdapter.Fill loads data; SqlCommandBuilder auto-generates INSERT/UPDATE/DELETE commands for simple single-table scenarios. Typed DataSets (via .xsd designer) provide compile-time type checking and IntelliSense. DataView filters and sorts without modifying the underlying DataTable. For modern applications, prefer Entity Framework or Dapper over raw DataSets — they're more maintainable and testable. DataSets remain useful for reporting and legacy interop.
Imports System.Data.SqlClient
' fill DataTable
Dim dt As New DataTable()
Using adapter As New SqlDataAdapter("SELECT * FROM Products", connStr)
adapter.Fill(dt)
End Using
' access rows
For Each row As DataRow In dt.Rows
Console.WriteLine(row("Name"))
Console.WriteLine(row.Field(Of Integer)("Price"))
Next
' modify and update
Dim newRow = dt.NewRow()
newRow("Name") = "Widget"
newRow("Price") = 9.99
dt.Rows.Add(newRow)
Using adapter As New SqlDataAdapter("SELECT * FROM Products", connStr),
builder As New SqlCommandBuilder(adapter)
adapter.Update(dt) ' generates INSERT/UPDATE/DELETE
End Using
' typed DataSet (strongly typed)
' add .xsd file to project, drag tables from Server Explorer
' Dim ta As New ProductsTableAdapter()
' Dim products = ta.GetData()
' For Each p In products
' Console.WriteLine(p.ProductName)
' Next
' filter and sort
Dim view As New DataView(dt)
view.RowFilter = "Price > 10"
view.Sort = "Price DESC"
For Each row As DataRowView In view
Console.WriteLine(row("Name"))
NextTransactions & Connection Pooling
Transactions ensure atomicity — all operations succeed or all fail. SqlTransaction wraps multiple commands on one connection. TransactionScope enables distributed transactions across multiple connections/resources (uses MS DTC if needed) — call scope.Complete() to commit. Connection pooling is automatic in ADO.NET: connections are reused rather than recreated, dramatically improving performance. Always Close/Dispose connections (Using blocks) to return them to the pool. Configure pool size and lifetime in the connection string for high-throughput apps.
Imports System.Data.SqlClient
' explicit transaction
Using conn As New SqlConnection(connStr)
conn.Open()
Using tran As SqlTransaction = conn.BeginTransaction()
Try
Using cmd1 As New SqlCommand(
"UPDATE Accounts SET Balance = Balance - 100 WHERE Id = 1",
conn, tran)
cmd1.ExecuteNonQuery()
End Using
Using cmd2 As New SqlCommand(
"UPDATE Accounts SET Balance = Balance + 100 WHERE Id = 2",
conn, tran)
cmd2.ExecuteNonQuery()
End Using
tran.Commit()
Catch ex As Exception
tran.Rollback()
Throw
End Try
End Using
End Using
' transaction scope (distributed, multi-resource)
Imports System.Transactions
Using scope As New TransactionScope()
' multiple connections, even different databases
Using conn1 As New SqlConnection(connStr1)
' operations on DB1
End Using
Using conn2 As New SqlConnection(connStr2)
' operations on DB2
End Using
scope.Complete() ' commit all
End Using ' rollback if Complete not called
' connection pooling (automatic, configure in connection string)
' "Server=...;Pooling=True;Max Pool Size=100;Connection Lifetime=300"
' connections are reused — always Close/Dispose to return to poolAsync Database Operations
Async database operations (OpenAsync, ExecuteReaderAsync, ReadAsync) prevent UI freezing during long queries. The pattern mirrors sync ADO.NET but with Await. Always use Async in UI applications to keep them responsive. For high-throughput servers, async DB calls free threads to handle other requests. MARS (Multiple Active Result Sets) allows multiple readers on one connection — enable with 'MultipleActiveResultSets=True' in the connection string. Handle SqlException for database-specific errors (deadlocks, constraint violations).
Imports System.Data.SqlClient
' async query
Async Function GetUsersAsync(minAge As Integer) As Task(Of List(Of User))
Dim users As New List(Of User)()
Using conn As New SqlConnection(connStr)
Await conn.OpenAsync()
Using cmd As New SqlCommand("SELECT Id, Name, Age FROM Users WHERE Age > @age", conn)
cmd.Parameters.AddWithValue("@age", minAge)
Using reader = Await cmd.ExecuteReaderAsync()
While Await reader.ReadAsync()
users.Add(New User With {
.Id = reader.GetInt32(0),
.Name = reader.GetString(1),
.Age = reader.GetInt32(2)
})
End While
End Using
End Using
End Using
Return users
End Function
' async execute (INSERT/UPDATE)
Async Function UpdateUserAsync(user As User) As Task(Of Integer)
Using conn As New SqlConnection(connStr)
Await conn.OpenAsync()
Using cmd As New SqlCommand(
"UPDATE Users SET Name=@name, Age=@age WHERE Id=@id", conn)
cmd.Parameters.AddWithValue("@name", user.Name)
cmd.Parameters.AddWithValue("@age", user.Age)
cmd.Parameters.AddWithValue("@id", user.Id)
Return Await cmd.ExecuteNonQueryAsync()
End Using
End Using
End Function
' usage in event handler
Private Async Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
btnLoad.Enabled = False
Try
Dim users = Await GetUsersAsync(25)
DataGridView1.DataSource = users
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
btnLoad.Enabled = True
End Try
End SubDapper Micro-ORM
Dapper is a micro-ORM that extends IDbConnection with extension methods — fast (near-raw ADO.NET speed) and simple. Query<T> maps rows to objects automatically by matching column names to properties. Anonymous objects provide parameters (SQL injection-safe). Bulk insert passes a list and Dapper executes once per item. Multi-mapping handles joins by splitting rows on a column. Dapper is ideal when you want SQL control with less boilerplate than raw ADO.NET. For complex object graphs and change tracking, use Entity Framework instead.
Imports Dapper ' NuGet: Install-Package Dapper
Imports System.Data.SqlClient
' simple query
Using conn As New SqlConnection(connStr)
Dim users = conn.Query(Of User)("SELECT * FROM Users WHERE Age > @age",
New With { .age = 25 })
For Each u In users
Console.WriteLine(u.Name)
Next
End Using
' single row
Using conn As New SqlConnection(connStr)
Dim user = conn.QuerySingleOrDefault(Of User)(
"SELECT * FROM Users WHERE Id = @id", New With { .id = 42 })
End Using
' insert
Using conn As New SqlConnection(connStr)
Dim sql = "INSERT INTO Users (Name, Age, Email) VALUES (@Name, @Age, @Email)"
conn.Execute(sql, New With { .Name = "Alice", .Age = 30, .Email = "[email protected]" })
End Using
' bulk insert
Using conn As New SqlConnection(connStr)
Dim users = New List(Of User) From {
New User With {.Name = "A", .Age = 1},
New User With {.Name = "B", .Age = 2}
}
conn.Execute("INSERT INTO Users (Name, Age) VALUES (@Name, @Age)", users)
End Using
' multi-mapping (join)
Dim sql = "SELECT * FROM Orders o INNER JOIN Users u ON o.UserId = u.Id"
Dim orders = conn.Query(Of Order, User, Order)(sql,
Function(o, u)
o.User = u
Return o
End Function, splitOn:="Id")
' stored procedure
Dim result = conn.Query(Of User)("sp_GetActiveUsers",
commandType:=Data.CommandType.StoredProcedure)LINQ to Objects
Query & Method Syntax
LINQ provides two syntaxes: query (SQL-like, From...Where...Select) and method (fluent, .Where().Select()). They compile to the same IL — choose based on readability. Query syntax supports fewer operators (no Sum, Count directly); use method syntax for those. LINQ uses deferred execution — the query isn't run until you iterate (For Each) or call a materializing operator (ToList, Count). This means modifying the source after defining the query affects results. Use .ToList() to force immediate execution and snapshot the data.
Dim numbers = New List(Of Integer) From {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
' query syntax (SQL-like)
Dim evens = From n In numbers
Where n Mod 2 = 0
Select n
Order By n Descending
' method syntax (fluent)
Dim evens2 = numbers.Where(Function(n) n Mod 2 = 0).
OrderByDescending(Function(n) n)
' both produce the same result
For Each n In evens
Console.WriteLine(n) ' 10, 8, 6, 4, 2
Next
' projection (transform)
Dim squares = numbers.Select(Function(n) n * n)
Dim names = numbers.Select(Function(n) $"Number {n}")
' filtering with multiple conditions
Dim filtered = From n In numbers
Where n > 3 AndAlso n < 8
Select n
' type inference (Dim is required)
Dim result = From p In people
Where p.Age >= 18
Select p.Name, p.Age
Order By Age
' deferred execution (query runs when iterated)
Dim query = numbers.Where(Function(n) n > 5) ' not executed yet
numbers.Add(11) ' query will include 11!
For Each n In query ' executes now
NextAggregation & Grouping
LINQ aggregation operators (Sum, Average, Min, Max, Count, Any, All) collapse sequences to single values. GroupBy partitions elements by a key; each group has a .Key and is itself enumerable. The query syntax 'Group By...Into Group' is VB-specific. Multiple aggregates per group are common for reporting. Any/All short-circuit (stop early) — useful for checking conditions without full iteration. Aggregate functions throw on empty sequences; use the nullable variants (Sum returns 0, Average throws) or DefaultIfEmpty to handle this.
Dim people = New List(Of Person) From {
New Person With {.Name = "Al", .Dept = "Eng", .Salary = 90000},
New Person With {.Name = "Bo", .Dept = "Eng", .Salary = 70000},
New Person With {.Name = "Cy", .Dept = "Sales", .Salary = 60000}
}
' aggregation
Dim total = people.Sum(Function(p) p.Salary)
Dim avg = people.Average(Function(p) p.Salary)
Dim max = people.Max(Function(p) p.Salary)
Dim count = people.Count(Function(p) p.Dept = "Eng")
Dim any = people.Any(Function(p) p.Salary > 80000)
Dim all = people.All(Function(p) p.Salary > 0)
' grouping
Dim byDept = From p In people
Group p By p.Dept Into Group
Select Dept, Count = Group.Count(), Avg = Group.Average(Function(x) x.Salary)
For Each g In byDept
Console.WriteLine($"{g.Dept}: {g.Count} people, avg ${g.Avg}")
Next
' method syntax grouping
Dim grouped = people.GroupBy(Function(p) p.Dept)
For Each g In grouped
Console.WriteLine($"{g.Key}: {g.Count()} people")
Next
' group with multiple aggregates
Dim stats = people.GroupBy(Function(p) p.Dept).
Select(Function(g) New With {
.Dept = g.Key,
.Count = g.Count(),
.Total = g.Sum(Function(p) p.Salary),
.Min = g.Min(Function(p) p.Salary)
})Joining, Zip & Set Operations
Join performs inner joins (matching keys); Group Join with DefaultIfEmpty creates left joins. Zip pairs elements position-by-position. Set operations (Union, Intersect, Except, Distinct) use default equality comparers — override Equals/GetHashCode or pass a custom IEqualityComparer for complex types. Concat appends without removing duplicates (unlike Union). All these are lazy except when materialized. For large datasets, consider using a HashSet or Dictionary for O(1) lookups instead of Join's O(n*m) nested loops.
Dim employees = New List(Of Employee) From {
New Employee With {.Id = 1, .Name = "Al", .DeptId = 10},
New Employee With {.Id = 2, .Name = "Bo", .DeptId = 20}
}
Dim departments = New List(Of Department) From {
New Department With {.Id = 10, .Name = "Eng"},
New Department With {.Id = 20, .Name = "Sales"},
New Department With {.Id = 30, .Name = "HR"}
}
' inner join
Dim result = From e In employees
Join d In departments On e.DeptId Equals d.Id
Select e.Name, Dept = d.Name
' group join (left join with grouped results)
Dim leftJoin = From d In departments
Group Join e In employees On d.Id Equals e.DeptId Into Group
From e In Group.DefaultIfEmpty()
Select Dept = d.Name, Emp = If(e Is Nothing, "Vacant", e.Name)
' method syntax join
Dim joined = employees.Join(departments,
Function(e) e.DeptId,
Function(d) d.Id,
Function(e, d) New With { .Name = e.Name, .Dept = d.Name })
' zip (pair elements)
Dim nums = {1, 2, 3}
Dim words = {"one", "two", "three"}
Dim pairs = nums.Zip(words, Function(n, w) $"{n}={w}")
' set operations
Dim a = {1, 2, 3, 4}
Dim b = {3, 4, 5, 6}
Dim union = a.Union(b) ' 1,2,3,4,5,6
Dim intersect = a.Intersect(b) ' 3,4
Dim except = a.Except(b) ' 1,2
Dim concat = a.Concat(b) ' 1,2,3,4,3,4,5,6
Dim distinct = concat.Distinct() ' 1,2,3,4,5,6Sorting, Paging & Element Operations
OrderBy/ThenBy sort (stable); OrderByDescending/ThenByDescending sort descending. Multiple sort keys use ThenBy after OrderBy. Paging uses Skip (offset) and Take (limit) — essential for large datasets in UIs. First/Last throw on empty; FirstOrDefault returns the default value (Nothing for reference types, 0 for numbers). Single enforces exactly one match (throws otherwise) — useful for validation. TakeWhile/SkipWhile partition based on a predicate until it fails. All these are lazy except First/Last/Single which force iteration.
Dim nums = {5, 2, 8, 1, 9, 3, 7, 4, 6}
' sorting
Dim asc = nums.OrderBy(Function(n) n)
Dim desc = nums.OrderByDescending(Function(n) n)
Dim multi = people.OrderBy(Function(p) p.Dept).
ThenBy(Function(p) p.Name)
Dim multiDesc = people.OrderByDescending(Function(p) p.Dept).
ThenByDescending(Function(p) p.Salary)
' reverse
Dim reversed = nums.Reverse()
' paging (skip + take)
Dim page = nums.Skip(5).Take(3) ' elements 6,7,8 (0-indexed)
Dim firstPage = nums.Take(5) ' first 5
' element operations
Dim first = nums.First() ' throws if empty
Dim firstOr = nums.FirstOrDefault() ' default (0) if empty
Dim last = nums.Last()
Dim single = nums.Single(Function(n) n = 5) ' throws if not exactly one
Dim singleOr = nums.SingleOrDefault(Function(n) n = 99) ' 0 if not found
' element at position
Dim third = nums.ElementAt(2) ' throws if out of range
Dim thirdOr = nums.ElementAtOrDefault(99) ' 0 if out of range
' find index (not LINQ, but related)
Dim idx = Array.FindIndex(nums, Function(n) n > 5)
' partition
Dim large = nums.Where(Function(n) n > 5)
Dim small = nums.TakeWhile(Function(n) n < 8) ' stops at first false
Dim skipSmall = nums.SkipWhile(Function(n) n < 8)Custom LINQ & Expression Trees
Extension methods (decorated with <Extension()>) add LINQ-like operators to any type. Custom aggregations (Median, Mode, StandardDeviation) fill gaps in the built-in operators. IEnumerable(Of T) uses delegates (in-memory); IQueryable(Of T) uses expression trees that translate to SQL (EF/LINQ to SQL) — mixing them can cause client-side evaluation (slow). ToList/ToArray/ToDictionary force evaluation and snapshot results. PLINQ (.AsParallel) parallelizes queries — use for CPU-bound operations on large collections, but beware of ordering and thread safety.
' custom extension method
Imports System.Runtime.CompilerServices
<Extension()>
Public Function WhereNot(Of T)(source As IEnumerable(Of T),
predicate As Func(Of T, Boolean)) As IEnumerable(Of T)
Return source.Where(Function(x) Not predicate(x))
End Function
' usage
Dim odds = numbers.WhereNot(Function(n) n Mod 2 = 0)
' custom aggregation
<Extension()>
Public Function Median(source As IEnumerable(Of Double)) As Double
Dim sorted = source.OrderBy(Function(x) x).ToList()
Dim count = sorted.Count
If count = 0 Then Return 0
If count Mod 2 = 0 Then
Return (sorted(count 2 - 1) + sorted(count 2)) / 2
Else
Return sorted(count 2)
End If
End Function
' usage
Dim med = numbers.Median()
' IEnumerable vs IQueryable
' IEnumerable: LINQ to Objects (in-memory, delegates)
' IQueryable: LINQ to SQL/EF (translates to SQL, expression trees)
' force evaluation
Dim list = query.ToList() ' List(Of T)
Dim array = query.ToArray() ' T()
Dim dict = query.ToDictionary(Function(x) x.Id)
Dim lookup = query.ToLookup(Function(x) x.Category)
' PLINQ (parallel)
Dim parallel = nums.AsParallel().
Where(Function(n) IsPrime(n)).
Select(Function(n) n * 2).
ToList()Multithreading & Async
Tasks & Async/Await
Async/Await is the modern way to write asynchronous code — it looks synchronous but doesn't block threads. Mark methods Async and use Await on Task-returning calls. UI event handlers can be Async Sub (the only place Async Sub is acceptable). Task.Run offloads CPU-bound work to the thread pool. Task.WhenAll waits for all tasks (parallel); Task.WhenAny waits for the first to complete. The compiler generates a state machine that handles continuations, exception propagation, and context capture. Always prefer Async/Await over manual threading or callbacks.
Imports System.Threading.Tasks
' async function
Async Function DownloadAsync(url As String) As Task(Of String)
Using client As New Net.Http.HttpClient()
Return Await client.GetStringAsync(url)
End Function
End Function
' call async from event handler
Private Async Sub btnDownload_Click(sender As Object, e As EventArgs) Handles btnDownload.Click
btnDownload.Enabled = False
Try
Dim content = Await DownloadAsync("https://example.com")
txtResult.Text = content
Catch ex As Exception
MessageBox.Show(ex.Message)
Finally
btnDownload.Enabled = True
End Try
End Sub
' run CPU-bound work on background thread
Async Function ProcessDataAsync(data As Byte()) As Task(Of Result)
Return Await Task.Run(Function()
' CPU-intensive work
Return Analyze(data)
End Function)
End Function
' Task.Run overloads
Dim t1 = Task.Run(Function() Compute(1))
Dim t2 = Task.Run(Async Function() Await FetchAsync())
' wait for multiple tasks
Dim tasks = New List(Of Task) From {t1, t2, t3}
Await Task.WhenAll(tasks)
' wait for any
Dim firstDone = Await Task.WhenAny(t1, t2, t3)
' task continuation
Dim result = Await t1.ContinueWith(Function(t) t.Result * 2)Thread Safety & Synchronization
SyncLock (VB) / lock (C#) provides mutual exclusion — only one thread enters the block at a time. Lock on a private Object, never on Me or a string. Interlocked provides atomic operations without locks (faster for simple cases). Mutex works across processes (single-instance apps). SemaphoreSlim limits concurrent access (e.g., max 3 API calls at once). Always release locks/semaphores in Finally blocks. Deadlocks occur when two threads wait for each other — acquire locks in a consistent order. Use ConcurrentDictionary, ConcurrentQueue for lock-free thread-safe collections.
Imports System.Threading
' lock (mutual exclusion)
Private ReadOnly _lock As New Object
Private _counter As Integer = 0
Sub Increment()
SyncLock _lock
_counter += 1
End SyncLock
End Sub
' Monitor (equivalent to SyncLock)
Monitor.Enter(_lock)
Try
_counter += 1
Finally
Monitor.Exit(_lock)
End Try
' Interlocked (atomic operations, no lock needed)
Interlocked.Increment(_counter)
Interlocked.Add(_counter, 10)
Interlocked.Exchange(_counter, 0)
Interlocked.CompareExchange(_counter, 1, 0) ' if 0, set to 1
' Mutex (cross-process)
Dim mutex As New Mutex(False, "GlobalMyAppMutex")
mutex.WaitOne()
Try
' critical section
Finally
mutex.ReleaseMutex()
End Try
' SemaphoreSlim (limit concurrent access)
Dim sem As New SemaphoreSlim(3) ' max 3 concurrent
Await sem.WaitAsync()
Try
' limited concurrency
Finally
sem.Release()
End Try
' CountdownEvent
Dim cde As New CountdownEvent(5)
Parallel.For(0, 5, Sub(i)
' work
cde.Signal()
End Sub)
cde.Wait()Parallel & BackgroundWorker
Parallel.For/ForEach partition work across thread pool threads — ideal for CPU-bound loops. PLINQ (.AsParallel) parallelizes LINQ queries. Use ParallelOptions.MaxDegreeOfParallelism to limit threads. CancellationToken enables cooperative cancellation. BackgroundWorker is legacy but convenient for WinForms — it marshals ProgressChanged and RunWorkerCompleted to the UI thread automatically. For new code, prefer Task.Run + IProgress(Of T) + Async/Await. Parallel loops are blocking (unlike Async); don't use them on UI threads.
Imports System.Threading.Tasks
' Parallel.For (CPU-bound parallelism)
Parallel.For(0, 1000, Sub(i)
Process(i)
End Sub)
' Parallel.ForEach
Parallel.ForEach(files, Sub(f)
Compress(f)
End Sub)
' with options
Dim opts As New ParallelOptions With {.MaxDegreeOfParallelism = 4}
Parallel.For(0, 1000, opts, Sub(i) Process(i))
' PLINQ
Dim results = numbers.AsParallel().
Where(Function(n) IsPrime(n)).
Select(Function(n) n * 2).
ToArray()
' cancel parallel work
Dim cts As New CancellationTokenSource()
Parallel.For(0, 1000,
New ParallelOptions With {.CancellationToken = cts.Token},
Sub(i, state)
If SomeCondition Then state.Break()
Process(i)
End Sub)
' BackgroundWorker (legacy, but simple for WinForms)
Dim bgw As New BackgroundWorker With {.WorkerReportsProgress = True, .WorkerSupportsCancellation = True}
AddHandler bgw.DoWork, Sub(sender, e)
For i = 1 To 100
If bgw.CancellationPending Then
e.Cancel = True
Return
End If
bgw.ReportProgress(i)
Thread.Sleep(50)
Next
End Sub
AddHandler bgw.ProgressChanged, Sub(sender, e)
ProgressBar1.Value = e.ProgressPercentage
End Sub
AddHandler bgw.RunWorkerCompleted, Sub(sender, e)
MessageBox.Show("Done!")
End Sub
bgw.RunWorkerAsync()Timers & UI Thread Marshaling
There are three Timers in .NET: Windows.Forms.Timer (UI thread, simplest for forms), System.Threading.Timer (thread pool, lightweight), and System.Timers.Timer (component, server scenarios). Only Forms.Timer can update UI directly; others require Invoke. Control.Invoke marshals a delegate to the UI thread — check InvokeRequired first. ConfigureAwait(False) improves performance in library code (no context capture) but prevents UI access afterward. SynchronizationContext.Post is a more general way to marshal to a specific context (UI, ASP.NET).
' Windows.Forms.Timer (UI thread, simple)
Dim timer1 As New Windows.Forms.Timer With {.Interval = 1000}
AddHandler timer1.Tick, Sub(sender, e)
Label1.Text = Date.Now.ToString()
End Sub
timer1.Start()
' System.Threading.Timer (thread pool, not UI thread)
Dim timer2 As New Threading.Timer(
Sub(state)
' runs on thread pool thread!
' must marshal to UI thread to update controls:
Me.Invoke(Sub() Label1.Text = Date.Now.ToString())
End Sub,
Nothing, 0, 1000)
' System.Timers.Timer (component, fires on thread pool)
Dim timer3 As New Timers.Timer With {.Interval = 1000, .AutoReset = True}
AddHandler timer3.Elapsed, Sub(sender, e)
Me.Invoke(Sub() Label1.Text = Date.Now.ToString())
End Sub
timer3.Start()
' UI thread marshaling
If Label1.InvokeRequired Then
Label1.Invoke(Sub() Label1.Text = "updated")
Else
Label1.Text = "updated"
End If
' async with ConfigureAwait
Await SomeAsync().ConfigureAwait(True) ' capture context (default)
Await SomeAsync().ConfigureAwait(False) ' don't capture (faster, but no UI access)
' SynchronizationContext for custom marshaling
Dim uiContext = SynchronizationContext.Current
uiContext.Post(Sub() Label1.Text = "done", Nothing)Concurrent Collections & Channels
Concurrent collections (ConcurrentDictionary, Queue, Stack, Bag) are thread-safe alternatives to regular collections — use them instead of locking. AddOrUpdate/GetOrAdd on ConcurrentDictionary are atomic compound operations. BlockingCollection implements the producer-consumer pattern with blocking Add/Take — great for worker queues. Channels (System.Threading.Channels) are the modern, async-friendly alternative with backpressure. For UI thread safety, use Invoke/BeginInvoke rather than concurrent collections. Always prefer these built-in primitives over manual locking — they're tested and optimized.
Imports System.Collections.Concurrent
' ConcurrentDictionary (thread-safe dictionary)
Dim dict As New ConcurrentDictionary(Of String, Integer)
dict.TryAdd("a", 1)
dict.AddOrUpdate("a", 1, Function(key, old) old + 1) ' atomic
Dim val = dict.GetOrAdd("b", Function(key) ComputeValue(key))
' ConcurrentQueue (FIFO, thread-safe)
Dim queue As New ConcurrentQueue(Of String)
queue.Enqueue("first")
Dim item As String
If queue.TryDequeue(item) Then
Console.WriteLine(item)
End If
' ConcurrentStack (LIFO)
Dim stack As New ConcurrentStack(Of Integer)
stack.Push(1)
Dim popped As Integer
stack.TryPop(popped)
' ConcurrentBag (unordered, fast for producer=consumer)
Dim bag As New ConcurrentBag(Of Integer)
bag.Add(42)
' BlockingCollection (producer-consumer pattern)
Dim bc As New BlockingCollection(Of Integer)(boundedCapacity:=100)
' producer
Task.Run(Sub()
For i = 1 To 1000
bc.Add(i) ' blocks if full
Next
bc.CompleteAdding()
End Sub)
' consumer
Task.Run(Sub()
For Each x In bc.GetConsumingEnumerable() ' blocks if empty
Process(x)
Next
End Sub)
' Channels (modern, high-performance)
' NuGet: System.Threading.Channels
Dim channel = System.Threading.Channels.Channel.CreateBounded(Of String)(100)
Await channel.Writer.WriteAsync("message")
Dim msg = Await channel.Reader.ReadAsync()Registry & Windows API
Registry Read & Write
The Windows Registry stores application settings and system configuration. Use Microsoft.Win32.Registry for access. HKEY_CURRENT_USER (HKCU) is per-user (no admin needed); HKEY_LOCAL_MACHINE (HKLM) is system-wide (requires admin). Always provide a default value when reading (returns Nothing if missing). Use RegistryValueKind to specify type (String, DWord, Binary). The Run key auto-starts apps on login. Wrap RegistryKey in Using to close handles. Be cautious editing the registry — mistakes can break Windows.
Imports Microsoft.Win32
' read a value
Dim value As String = Registry.GetValue(
"HKEY_CURRENT_USERSoftwareMyApp",
"Setting1",
"default") ' default if not found
Console.WriteLine(value)
' write a value
Registry.SetValue(
"HKEY_CURRENT_USERSoftwareMyApp",
"Setting1",
"my value")
' using RegistryKey (more control)
Using key As RegistryKey = Registry.CurrentUser.OpenSubKey("SoftwareMyApp", writable:=True)
If key IsNot Nothing Then
Dim setting = key.GetValue("Setting1")
key.SetValue("Setting2", 42, RegistryValueKind.DWord)
key.DeleteValue("Setting2", throwOnMissingValue:=False)
End If
End Using
' create subkey
Using key As RegistryKey = Registry.CurrentUser.CreateSubKey("SoftwareMyAppSub")
key.SetValue("Name", "Test")
End Using
' enumerate subkeys and values
Using key As RegistryKey = Registry.LocalMachine.OpenSubKey("SOFTWARE")
For Each subkeyName In key.GetSubKeyNames()
Console.WriteLine(subkeyName)
Next
For Each valueName In key.GetValueNames()
Console.WriteLine($"{valueName}: {key.GetValue(valueName)}")
Next
End Using
' run on startup (add to Run key)
Registry.SetValue("HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionRun",
"MyApp", Application.ExecutablePath)P/Invoke: Calling Windows API
P/Invoke (Platform Invoke) lets VB call unmanaged Windows API functions via DllImport. Declare the function signature matching the C API; the runtime marshals types automatically (String ↔ LPSTR, Integer ↔ DWORD). Use ByRef for output parameters and StructLayout for structures passed by reference. CharSet.Auto selects ANSI or Unicode based on the OS. Common libraries: user32 (windows, messages), kernel32 (system, files), gdi32 (graphics). Always wrap P/Invoke in a NativeMethods class. Pinvoke.net is a great resource for signatures.
Imports System.Runtime.InteropServices
Public Class NativeMethods
' MessageBox from user32.dll
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Public Shared Function MessageBox(hWnd As IntPtr, text As String,
caption As String, type As Integer) As Integer
End Function
' GetSystemMetrics
<DllImport("user32.dll")>
Public Shared Function GetSystemMetrics(nIndex As Integer) As Integer
End Function
' Beep
<DllImport("kernel32.dll")>
Public Shared Function Beep(frequency As Integer, duration As Integer) As Boolean
End Function
' SendMessage (for custom control messages)
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Public Shared Function SendMessage(hWnd As IntPtr, msg As Integer,
wParam As IntPtr, lParam As IntPtr) As IntPtr
End Function
' structures
<StructLayout(LayoutKind.Sequential)>
Public Structure POINT
Public X As Integer
Public Y As Integer
End Structure
<DllImport("user32.dll")>
Public Shared Function GetCursorPos(ByRef lpPoint As POINT) As Boolean
End Function
End Class
' usage
NativeMethods.MessageBox(IntPtr.Zero, "Hello from API!", "Test", 0)
NativeMethods.Beep(440, 500) ' 440 Hz for 500ms
Dim p As NativeMethods.POINT
NativeMethods.GetCursorPos(p)
Console.WriteLine($"Cursor at {p.X}, {p.Y}")
' screen dimensions
Dim width = NativeMethods.GetSystemMetrics(0) ' SM_CXSCREEN
Dim height = NativeMethods.GetSystemMetrics(1) ' SM_CYSCREENProcess & Shell Operations
Process.Start launches external programs. UseShellExecute=False with RedirectStandardOutput lets you capture output programmatically — essential for command-line tools. Always WaitForExit and read output to avoid deadlocks (the output buffer can fill and block the process). GetProcesses enumerates running processes; GetProcessesByName finds specific ones. Verb='runas' elevates to admin (triggers UAC). For long-running processes, subscribe to Exited event or use async patterns. Be cautious with user-supplied file paths — validate to prevent command injection.
Imports System.Diagnostics
' start a process
Process.Start("notepad.exe")
Process.Start("notepad.exe", "file.txt")
Process.Start("https://example.com") ' opens default browser
Process.Start("mailto:[email protected]")
' with ProcessStartInfo (more control)
Dim psi As New ProcessStartInfo With {
.FileName = "cmd.exe",
.Arguments = "/c dir",
.UseShellExecute = False,
.RedirectStandardOutput = True,
.CreateNoWindow = True
}
Using p As Process = Process.Start(psi)
Dim output = p.StandardOutput.ReadToEnd()
p.WaitForExit()
Console.WriteLine(output)
End Using
' async read output
Using p As Process = Process.Start(psi)
Dim output = Await p.StandardOutput.ReadToEndAsync()
Await Task.Run(Sub() p.WaitForExit())
End Using
' get running processes
For Each proc In Process.GetProcesses()
Console.WriteLine($"{proc.ProcessName} (PID {proc.Id})")
Next
' find specific process
Dim excel = Process.GetProcessesByName("EXCEL").FirstOrDefault()
If excel IsNot Nothing Then
Console.WriteLine($"Excel memory: {excel.WorkingSet64 1024} KB")
End If
' run as admin
psi.Verb = "runas" ' triggers UAC prompt
Process.Start(psi)Environment & System Information
Environment provides system and user information. GetFolderPath with SpecialFolder is the correct way to find standard directories (AppData, MyDocuments, Temp) — never hardcode paths. Environment variables configure behavior across machines. GetCommandLineArgs includes the executable as the first element. For detailed hardware info, use WMI (System.Management) — query classes like Win32_Processor, Win32_LogicalDisk. Screen.AllScreens (Windows Forms) gives monitor info for multi-display setups. Always use these APIs instead of guessing paths or settings.
' environment variables
Dim path = Environment.GetEnvironmentVariable("PATH")
Environment.SetEnvironmentVariable("MY_VAR", "value")
Dim allVars = Environment.GetEnvironmentVariables()
' special folders
Dim appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
Dim myDocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
Dim temp = Environment.GetFolderPath(Environment.SpecialFolder.Temp)
Dim programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
' system info
Console.WriteLine($"Machine: {Environment.MachineName}")
Console.WriteLine($"User: {Environment.UserName}")
Console.WriteLine($"OS: {Environment.OSVersion}")
Console.WriteLine($"64-bit: {Environment.Is64BitOperatingSystem}")
Console.WriteLine($"Processors: {Environment.ProcessorCount}")
Console.WriteLine($".NET: {Environment.Version}")
Console.WriteLine($"Uptime: {Environment.TickCount64 1000} seconds")
' command line args
Dim args = Environment.GetCommandLineArgs()
' args(0) is the executable path
' current directory
Environment.CurrentDirectory = "C: emp"
' system power
Console.WriteLine($"System uptime: {TimeSpan.FromMilliseconds(Environment.TickCount64)}")
' WMI for detailed hardware info
Imports System.Management
Dim searcher As New ManagementObjectSearcher("SELECT * FROM Win32_Processor")
For Each obj In searcher.Get()
Console.WriteLine($"CPU: {obj("Name")}")
Next
' screen info
Dim screens = Screen.AllScreens
For Each s In screens
Console.WriteLine($"{s.DeviceName}: {s.Bounds.Width}x{s.Bounds.Height}")
NextINI Files & Configuration
INI files are legacy but still used for simple configurations. The Windows API functions (WritePrivateProfileString, GetPrivateProfileString) handle parsing. For modern .NET, use app.config (XML) or appsettings.json (JSON) with ConfigurationManager or Microsoft.Extensions.Configuration. JSON config supports nested structures, arrays, and environment-specific overrides (appsettings.Production.json). Configuration builders can combine multiple sources (files, environment variables, command line). Always separate configuration from code for deployment flexibility.
' INI files (legacy but still common)
Imports System.Runtime.InteropServices
Public Class IniFile
Private path As String
<DllImport("kernel32.dll", CharSet:=CharSet.Unicode)>
Private Shared Function WritePrivateProfileString(section As String,
key As String, value As String, filePath As String) As Boolean
End Function
<DllImport("kernel32.dll", CharSet:=CharSet.Unicode)>
Private Shared Function GetPrivateProfileString(section As String,
key As String, defaultVal As String,
<Out> ByVal retVal As StringBuilder, size As Integer,
filePath As String) As Integer
End Function
Public Sub New(filePath As String)
path = filePath
End Sub
Public Function Read(section As String, key As String, Optional defaultVal As String = "") As String
Dim sb As New StringBuilder(255)
GetPrivateProfileString(section, key, defaultVal, sb, 255, path)
Return sb.ToString()
End Function
Public Sub Write(section As String, key As String, value As String)
WritePrivateProfileString(section, key, value, path)
End Sub
End Class
' usage
Dim ini As New IniFile("config.ini")
ini.Write("Settings", "Theme", "Dark")
Dim theme = ini.Read("Settings", "Theme", "Light")
' modern: app.config / appsettings.json
' app.config:
' <appSettings>
' <add key="Theme" value="Dark" />
' </appSettings>
Dim theme2 = Configuration.ConfigurationManager.AppSettings("Theme")
' JSON config (with System.Configuration or Microsoft.Extensions.Configuration)
' appsettings.json:
' { "Theme": "Dark", "MaxItems": 100 }Collections & Generics Deep
List, Dictionary & HashSet
List(Of T) is the workhorse — dynamic array with O(1) append, O(n) insert/remove. Dictionary(Of K,V) provides O(1) key lookup (hash table). HashSet(Of T) stores unique elements with fast set operations (Union, Intersect, Except). SortedDictionary keeps keys sorted (binary search tree). LinkedList is doubly-linked (fast insert/remove anywhere, but no indexed access). Choose based on access patterns: List for indexed, Dictionary for keyed, HashSet for membership, SortedDictionary for ordered iteration. All are generic (type-safe, no boxing).
Imports System.Collections.Generic
' List(Of T)
Dim list As New List(Of String) From {"a", "b", "c"}
list.Add("d")
list.AddRange({"e", "f"})
list.Insert(0, "first")
list.Remove("b")
list.RemoveAt(0)
list.Sort()
list.Reverse()
Dim count = list.Count
Dim hasA = list.Contains("a")
Dim idx = list.IndexOf("c")
Dim slice = list.GetRange(0, 2) ' first 2 elements
Dim arr = list.ToArray()
' Dictionary(Of TKey, TValue)
Dim dict As New Dictionary(Of String, Integer)
dict.Add("one", 1)
dict("two") = 2
dict.TryGetValue("one", Dim val As Integer) ' val = 1, returns True
For Each kvp In dict
Console.WriteLine($"{kvp.Key} = {kvp.Value}")
Next
Dim keys = dict.Keys.ToList()
Dim vals = dict.Values.ToList()
' HashSet(Of T) (unique elements, O(1) lookup)
Dim set1 As New HashSet(Of Integer) From {1, 2, 3}
Dim set2 As New HashSet(Of Integer) From {3, 4, 5}
set1.Add(3) ' no effect (already exists)
Dim union = New HashSet(Of Integer)(set1) : union.UnionWith(set2) ' 1,2,3,4,5
Dim inter = New HashSet(Of Integer)(set1) : inter.IntersectWith(set2) ' 3
Dim diff = New HashSet(Of Integer)(set1) : diff.ExceptWith(set2) ' 1,2
' SortedDictionary / SortedList (sorted by key)
Dim sorted As New SortedDictionary(Of String, Integer)
sorted("zebra") = 1
sorted("apple") = 2 ' iterates in sorted order
' LinkedList (fast insert/remove at ends)
Dim ll As New LinkedList(Of Integer)
ll.AddFirst(1)
ll.AddLast(2)Queue, Stack & ObservableCollection
Queue (FIFO) and Stack (LIFO) are specialized for ordered access patterns. ObservableCollection raises CollectionChanged events when items are added/removed/replaced — essential for MVVM data binding. BindingList extends this with editing capabilities (AddingNew, AllowEdit, AllowRemove). ReadOnlyCollection wraps a list to prevent modification (returns the original, not a copy). KeyedCollection combines list and dictionary semantics (access by index or key). For thread-safe versions, use ConcurrentQueue, ConcurrentStack, ConcurrentDictionary from System.Collections.Concurrent.
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
' Queue(Of T) (FIFO)
Dim queue As New Queue(Of String)
queue.Enqueue("first")
queue.Enqueue("second")
Dim next1 = queue.Dequeue() ' "first"
Dim peek = queue.Peek() ' "second" (doesn't remove)
Dim hasItems = queue.Count > 0
' Stack(Of T) (LIFO)
Dim stack As New Stack(Of Integer)
stack.Push(1)
stack.Push(2)
stack.Push(3)
Dim top = stack.Pop() ' 3
Dim peek = stack.Peek() ' 2
' ObservableCollection (notifies on changes - for data binding)
Dim people As New ObservableCollection(Of Person)()
AddHandler people.CollectionChanged, Sub(sender, e)
If e.Action = NotifyCollectionChangedAction.Add Then
Console.WriteLine($"Added: {CType(e.NewItems(0), Person).Name}")
End If
End Sub
people.Add(New Person With {.Name = "Alice"}) ' triggers event
' BindingList(Of T) (editable, for DataGridView)
Dim list As New BindingList(Of Person)()
list.AddingNew = Function() New Person()
list.RaiseListChangedEvents = True
' ReadOnlyCollection (wrapper, prevents modification)
Dim ro As New ReadOnlyCollection(Of String)(list)
' ro.Add("x") ' throws NotSupportedException
' KeyedCollection (abstract, dictionary-like by key)
Public Class PersonCollection
Inherits KeyedCollection(Of String, Person)
Protected Overrides Function GetKeyForItem(item As Person) As String
Return item.Name
End Function
End ClassGeneric Methods & Constraints
Generics provide type safety without boxing/unboxing overhead. Constraints (Of T As ...) restrict type parameters: Class (reference types), Structure (value types), New (parameterless constructor), IComparable (interface), or a base class. Multiple constraints use curly braces: Of T As {Class, New, IComparable(Of T)}. Generic methods infer type from arguments. Generics are reified in .NET (type info available at runtime, unlike Java's type erasure). Use generics for collections, algorithms, and utility classes to avoid code duplication while maintaining type safety.
' generic method
Public Function Max(Of T As IComparable(Of T))(a As T, b As T) As T
If a.CompareTo(b) > 0 Then Return a
Return b
End Function
Dim m1 = Max(3, 5) ' Integer
Dim m2 = Max("apple", "banana") ' String
Dim m3 = Max(3.14, 2.71) ' Double
' multiple type parameters
Public Function Pair(Of T1, T2)(first As T1, second As T2) As Tuple(Of T1, T2)
Return Tuple.Create(first, second)
End Function
' constraints
Public Class Repository(Of T As Class, New)
' T must be a reference type with parameterless constructor
Public Function Create() As T
Return New T()
End Function
End Class
' constraint types:
' Class - reference type
' Structure - value type
' New - has parameterless constructor
' IComparable(Of T) - implements interface
' BaseClass - inherits from base class
' generic method with interface constraint
Public Sub Sort(Of T As IComparable(Of T))(ByRef arr As T())
Array.Sort(arr)
End Sub
' multiple constraints
Public Function Process(Of T As {Class, IComparable(Of T), New})(items As List(Of T)) As T
Dim item As New T()
items.Sort()
Return items(0)
End Function
' generic class with method
Public Class Cache(Of TKey, TValue)
Private dict As New Dictionary(Of TKey, TValue)
Public Sub Add(key As TKey, value As TValue)
dict.Add(key, value)
End Sub
Public Function Get(key As TKey) As TValue
Return dict(key)
End Function
End ClassCustom Collections & IEnumerable
Implementing IEnumerable(Of T) enables For Each iteration. The Iterator/Yield pattern (VB 14+) generates state machines for lazy evaluation — values are produced on demand. This is memory-efficient for large or infinite sequences. CollectionBase is a legacy base class; prefer implementing ICollection(Of T) or inheriting from List(Of T) for new code. Custom collections make sense when you need special behavior (validation, notifications, lazy loading). For most cases, compose existing collections rather than building from scratch.
Imports System.Collections
Imports System.Collections.Generic
' implement IEnumerable (foreach support)
Public Class NumberSequence
Implements IEnumerable(Of Integer)
Private start, count As Integer
Public Sub New(start As Integer, count As Integer)
Me.start = start
Me.count = count
End Sub
Public Function GetEnumerator() As IEnumerator(Of Integer) _
Implements IEnumerable(Of Integer).GetEnumerator
For i = 0 To count - 1
Yield start + i
Next
End Function
Private Function GetEnumerator1() As IEnumerator _
Implements IEnumerable.GetEnumerator
Return GetEnumerator()
End Function
End Class
' usage
Dim seq As New NumberSequence(10, 5)
For Each n In seq
Console.WriteLine(n) ' 10, 11, 12, 13, 14
Next
' iterator with Yield (lazy evaluation)
Public Iterator Function Evens(max As Integer) As IEnumerable(Of Integer)
For i = 0 To max
If i Mod 2 = 0 Then Yield i
Next
End Function
' custom collection class
Public Class PersonList
Inherits CollectionBase
Default Public Property Item(index As Integer) As Person
Get
Return CType(List(index), Person)
End Get
Set(value As Person)
List(index) = value
End Set
End Property
Public Function Add(p As Person) As Integer
Return List.Add(p)
End Function
End Class
' implement ICollection(Of T) for full collection semantics
Public Class CircularBuffer(Of T)
Implements ICollection(Of T)
' ... implement all members
End ClassTuples & ValueTuple
ValueTuple (VB 15+) is the modern way to return multiple values — lightweight (value type), with named fields for readability. Tuple (System.Tuple) is a reference type with Item1/Item2 names only. ValueTuple supports deconstruction (Dim (a, b) = ...) and element-wise equality. Use ValueTuple for internal method returns and intermediate LINQ results. For public APIs, prefer records or classes with meaningful names. The discard pattern (_) ignores unwanted values. ValueTuple is especially useful in LINQ for anonymous projections that need to be returned from methods.
' Tuple (reference type, up to 8 items)
Dim t1 As New Tuple(Of Integer, String, Boolean)(1, "hello", True)
Console.WriteLine(t1.Item1) ' 1
Console.WriteLine(t1.Item2) ' "hello"
' ValueTuple (value type, lightweight, named fields)
Dim vt1 As (Id As Integer, Name As String, Active As Boolean) =
(1, "hello", True)
Console.WriteLine(vt1.Id) ' 1
Console.WriteLine(vt1.Name) ' "hello"
' without names (Item1, Item2, ...)
Dim vt2 = (1, "hello", True)
Console.WriteLine(vt2.Item1)
' return multiple values
Public Function FindMinMax(numbers As Integer()) As (Min As Integer, Max As Integer)
Return (numbers.Min(), numbers.Max())
End Function
Dim result = FindMinMax({3, 1, 4, 1, 5, 9})
Console.WriteLine($"Min: {result.Min}, Max: {result.Max}")
' deconstruction
Dim (id, name, active) = GetRecord()
Dim (min, max) = FindMinMax({1, 2, 3})
' tuple in LINQ
Dim stats = From p In people
Group By p.Dept Into Group
Select Dept,
Count = Group.Count(),
AvgSalary = Group.Average(Function(x) x.Salary)
' tuple comparison
Dim a = (1, 2)
Dim b = (1, 2)
Console.WriteLine(a = b) ' True (element-wise comparison)
' discard
Dim (first, _, last) = GetThreeValues() ' ignore middleDebugging & Diagnostics
Debug & Trace
Debug class compiles out in Release builds (uses ConditionalAttribute); Trace works in all builds. TraceListeners direct output to files, console, or event log. TraceSwitch (configurable via app.config) controls verbosity at runtime without recompiling. TraceSource provides named, granular tracing with event types (Error, Warning, Info, Verbose). Always use Trace for production diagnostics and Debug for development assertions. The Conditional attribute removes method calls entirely in Release builds — zero overhead. Configure listeners in app.config for deployment flexibility.
Imports System.Diagnostics
' Debug class (Debug builds only)
Debug.WriteLine("Debug message")
Debug.Assert(balance >= 0, "Balance is negative!")
Debug.WriteLineIf(verbose, "Verbose output")
Debug.Indent()
Debug.WriteLine("Indented")
Debug.Unindent()
' Trace class (Release builds too)
Trace.WriteLine("Trace message")
Trace.TraceInformation("Info: {0} at {1}", value, Date.Now)
Trace.TraceWarning("Warning: low memory")
Trace.TraceError("Error: connection failed")
' trace listeners
Trace.Listeners.Add(New TextWriterTraceListener("trace.log"))
Trace.Listeners.Add(New ConsoleTraceListener())
Trace.AutoFlush = True
' conditional tracing
#If DEBUG Then
Debug.WriteLine("Only in debug")
#End If
<Conditional("DEBUG")>
Public Sub DebugLog(msg As String)
Console.WriteLine(msg)
End Sub
' TraceSwitch (configurable level)
Dim ts As New TraceSwitch("MySwitch", "Application tracing")
' app.config:
' <switches><add name="MySwitch" value="3" /></switches>
' 0=Off, 1=Error, 2=Warning, 3=Info, 4=Verbose
If ts.TraceInfo Then Trace.WriteLine("Info message")
If ts.TraceVerbose Then Trace.WriteLine("Verbose message")
' TraceSource (named, granular)
Dim ts2 As New TraceSource("MyApp")
ts2.TraceEvent(TraceEventType.Error, 0, "Something failed")
ts2.Switch.Level = SourceLevels.AllDebugger & Breakpoints
Debugger attributes customize how objects appear in the debugger. DebuggerDisplay controls the summary string; DebuggerBrowsable controls member visibility (RootHidden flattens collections). DebuggerStepThrough/Hidden control stepping behavior. Conditional breakpoints pause only when a condition is true — essential for finding bugs in large loops. Tracepoints log messages without modifying code. The Immediate Window evaluates expressions at runtime. Edit and Continue lets you fix code without restarting. Master these debugger features to dramatically speed up debugging.
Imports System.Diagnostics
' break into debugger
Debugger.Break() ' if attached, pauses; otherwise prompts
' check if debugger attached
If Debugger.IsAttached Then
Console.WriteLine("Running in debugger")
End If
' launch debugger
Debugger.Launch()
' DebuggerDisplay attribute (custom display in watch window)
<DebuggerDisplay("Name = {Name}, Age = {Age}")>
Public Class Person
Public Property Name As String
Public Property Age As Integer
<DebuggerBrowsable(DebuggerBrowsableState.RootHidden)>
Public Property Items As Integer() = {1, 2, 3}
End Class
' DebuggerStepThrough (skip in F11 step-into)
<DebuggerStepThrough>
Public Sub TrivialMethod()
' debugger won't step into this
End Sub
' DebuggerHidden (not visible in debugger)
<DebuggerHidden>
Public Sub InternalHelper()
End Sub
' conditional breakpoints in IDE:
' Right-click breakpoint > Settings > Condition
' e.g., i == 42 or str.Contains("error")
' tracepoints (log message without code):
' Right-click > Actions > Log message
' "i = {i}, value = {value}"
' edit and continue:
' modify code while paused, continue (limited support)
' immediate window (while debugging):
' ? variableName
' ? methodCall()
' variableName = newValueException Handling Strategies
Catch specific exceptions before generic ones (most specific first). Use Throw (not Throw ex) to preserve the original stack trace. Exception filters (When clause) add conditions without catching — the exception propagates if the filter is false. Custom exceptions should inherit from Exception (not ApplicationException, which is deprecated), be Serializable, and implement the three constructors. Wrap low-level exceptions in domain-specific ones to abstract implementation details. Never catch and swallow exceptions silently — at minimum log them. Use global exception handlers (AppDomain.UnhandledException, Application.ThreadException) as a safety net.
' try/catch/finally
Try
RiskyOperation()
Catch ex As FileNotFoundException
' specific exception first
Logger.Error(ex, "File not found")
Throw New UserFriendlyException("Please check the file path", ex)
Catch ex As IOException
Logger.Error(ex, "IO error")
Throw
Catch ex As Exception
' generic last resort
Logger.Error(ex, "Unexpected error")
Throw
Finally
' always runs (cleanup)
resource?.Dispose()
End Try
' exception filters (VB 14+)
Catch ex As HttpException When ex.StatusCode = 404
' only catch 404s
End Try
' custom exception
<Serializable>
Public Class BusinessRuleException
Inherits Exception
Public Property RuleName As String
Public Sub New(message As String, ruleName As String)
MyBase.New(message)
RuleName = ruleName
End Sub
Public Sub New(message As String, ruleName As String, inner As Exception)
MyBase.New(message, inner)
RuleName = ruleName
End Sub
Protected Sub New(info As Runtime.Serialization.SerializationInfo,
context As Runtime.Serialization.StreamingContext)
MyBase.New(info, context)
RuleName = info.GetString("RuleName")
End Sub
Public Overrides Sub GetObjectData(info As Runtime.Serialization.SerializationInfo,
context As Runtime.Serialization.StreamingContext)
MyBase.GetObjectData(info, context)
info.AddValue("RuleName", RuleName)
End Sub
End ClassLogging & Event Log
The Windows Event Log is ideal for system-level events (service start/stop, critical errors) — requires admin to create the source. For application logging, use a structured logging framework: Serilog (modern, JSON-friendly), NLog, or log4net. Structured logging (logger.Information("User {UserId}...", id)) preserves data types for querying, unlike string concatenation. Log levels (Debug, Info, Warning, Error, Fatal) let you filter at runtime. Always include context (user ID, order ID) in logs for debugging. Performance counters monitor CPU, memory, and custom metrics in production.
Imports System.Diagnostics
' write to Windows Event Log
If Not EventLog.SourceExists("MyApp") Then
EventLog.CreateEventSource("MyApp", "Application")
End If
EventLog.WriteEntry("MyApp", "Application started", EventLogEntryType.Information)
EventLog.WriteEntry("MyApp", "Disk full!", EventLogEntryType.Error, 1001)
EventLog.WriteEntry("MyApp", "Retrying operation", EventLogEntryType.Warning, 2001)
' read from Event Log
Dim log As New EventLog("Application")
For Each entry In log.Entries.Cast(Of EventLogEntry)().
Where(Function(e) e.Source = "MyApp").
Take(10)
Console.WriteLine($"{entry.TimeGenerated}: {entry.Message}")
Next
' structured logging with Serilog (NuGet)
' Install-Package Serilog.Sinks.File
' Dim logger = New LoggerConfiguration().
' WriteTo.File("log.txt", rollingInterval:=RollingInterval.Day).
' CreateLogger()
' logger.Information("User {UserId} logged in", userId)
' logger.Error(ex, "Failed to process {OrderId}", orderId)
' log4net (alternative)
' <log4net>
' <appender name="FileAppender" type="log4net.Appender.FileAppender">
' <file value="log.txt" />
' </appender>
' </log4net>
' NLog (alternative)
' Dim logger = LogManager.GetCurrentClassLogger()
' logger.Info("Processing started")
' logger.Error(ex, "Processing failed")
' performance counters
Dim pc As New PerformanceCounter("Processor", "% Processor Time", "_Total")
Dim cpuUsage = pc.NextValue()
Thread.Sleep(1000)
cpuUsage = pc.NextValue() ' actual value (needs two samples)Unit Testing & TDD
Unit tests verify individual methods in isolation. MSTest (built-in), xUnit, and NUnit are the main frameworks. TestInitialize/TestCleanup run before/after each test. Assert methods verify outcomes (AreEqual, IsTrue, ThrowsException). DataTestMethod/DataRow parameterize tests with multiple inputs. ExpectedException (or Try/Catch/Assert.Fail) tests exception cases. Use Moq to mock dependencies (interfaces) — setup expected behavior and verify calls. TDD (Test-Driven Development) writes tests first, then code. Aim for high coverage of business logic; skip trivial property getters/setters. Run tests in CI/CD to catch regressions.
' MSTest (built-in)
Imports Microsoft.VisualStudio.TestTools.UnitTesting
<TestClass>
Public Class CalculatorTests
Private calc As Calculator
<TestInitialize>
Public Sub Setup()
calc = New Calculator()
End Sub
<TestCleanup>
Public Sub Cleanup()
' cleanup after each test
End Sub
<TestMethod>
Public Sub Add_TwoNumbers_ReturnsSum()
Dim result = calc.Add(2, 3)
Assert.AreEqual(5, result)
End Sub
<TestMethod>
Public Sub Divide_ByZero_ThrowsException()
Try
calc.Divide(10, 0)
Assert.Fail("Should have thrown")
Catch ex As DivideByZeroException
' expected
End Try
End Sub
<TestMethod, ExpectedException(GetType(DivideByZeroException))>
Public Sub Divide_ByZero_Throws()
calc.Divide(10, 0)
End Sub
<DataTestMethod>
<DataRow(1, 2, 3)>
<DataRow(10, 20, 30)>
<DataRow(-1, 1, 0)>
Public Sub Add_VariousInputs(a As Integer, b As Integer, expected As Integer)
Assert.AreEqual(expected, calc.Add(a, b))
End Sub
End Class
' xUnit (alternative)
' <Fact> instead of <TestMethod>
' <Theory> with <InlineData> instead of <DataTestMethod>/<DataRow>
' Moq for mocking
' Dim mockRepo = New Mock(Of IUserRepository)()
' mockRepo.Setup(Function(r) r.GetById(1)).Returns(New User With {.Name = "Test"})
' Dim service = New UserService(mockRepo.Object)Collections & Dictionaries
ArrayList and List(Of T)
List(Of T) is the modern, type-safe replacement for ArrayList. Always prefer it — you get compile-time type checking and better performance (no boxing for value types). The From clause initializes the list inline. FindAll/Find use predicates (lambdas). AddRange appends multiple items at once.
' ArrayList (loosely typed, legacy)
Dim arr As New ArrayList()
arr.Add("hello")
arr.Add(42)
arr.Add(3.14)
arr.Remove("hello")
arr.RemoveAt(0)
For Each item In arr
Console.WriteLine(item)
Next
' List(Of T) — strongly typed, preferred
Dim nums As New List(Of Integer) From {1, 2, 3, 4, 5}
nums.Add(6)
nums.AddRange({7, 8, 9})
nums.Remove(3)
nums.RemoveAt(0)
nums.Sort()
nums.Reverse()
' find and convert
Dim evens = nums.FindAll(Function(n) n Mod 2 = 0)
Dim firstEven = nums.Find(Function(n) n Mod 2 = 0)
Dim arr2 = nums.ToArray()
' index
Dim idx = nums.IndexOf(5)
Console.WriteLine($"Count: {nums.Count}, Contains 5: {nums.Contains(5)}")Dictionary
Dictionary(Of TKey, TValue) is the standard hash map. Use TryGetValue to safely get a value (returns False if missing, no exception). Add throws if the key exists; indexer (dict(key)) silently overwrites. Keys/Values collections let you iterate or extract. LINQ works on dictionaries — they're IEnumerable(Of KeyValuePair).
' Dictionary(Of TKey, TValue)
Dim ages As New Dictionary(Of String, Integer) From {
{"Alice", 30},
{"Bob", 25}
}
' add and update
ages("Carol") = 42 ' add or update
ages.Add("Dave", 35) ' throws if key exists
ages.Remove("Bob")
' access
If ages.ContainsKey("Alice") Then
Console.WriteLine(ages("Alice"))
End If
' safe access
Dim age As Integer
If ages.TryGetValue("Eve", age) Then
Console.WriteLine($"Eve is {age}")
End If
' iterate
For Each kvp In ages
Console.WriteLine($"{kvp.Key}: {kvp.Value}")
Next
' keys and values
Dim names = ages.Keys.ToList()
Dim allAges = ages.Values.ToList()
' LINQ on dictionaries
Dim adults = ages.Where(Function(kvp) kvp.Value >= 30).ToDictionary(Function(kvp) kvp.Key, Function(kvp) kvp.Value)HashSet and SortedSet
HashSet is for fast membership testing and set operations (union, intersect, except). SortedSet keeps elements in sorted order (binary search tree). Both have O(1) or O(log n) operations — much faster than List.Contains (O(n)). Use HashSet when order doesn't matter; SortedSet when you need ordered iteration.
' HashSet(Of T) — unique elements, fast lookup
Dim set1 As New HashSet(Of Integer) From {1, 2, 3, 4, 5}
Dim set2 As New HashSet(Of Integer) From {3, 4, 5, 6, 7}
set1.Add(6) ' adds only if not present
set1.Remove(1)
' set operations
Dim union = New HashSet(Of Integer)(set1)
union.UnionWith(set2) ' {1,2,3,4,5,6,7}
Dim intersect = New HashSet(Of Integer)(set1)
intersect.IntersectWith(set2) ' {3,4,5,6}
Dim diff = New HashSet(Of Integer)(set1)
diff.ExceptWith(set2) ' {1,2}
Dim symDiff = New HashSet(Of Integer)(set1)
symDiff.SymmetricExceptWith(set2) ' {1,2,7}
' checks
set1.IsSubsetOf(set2)
set1.IsSupersetOf(set2)
set1.Overlaps(set2) ' any common elements
' SortedSet keeps elements sorted
Dim sorted As New SortedSet(Of Integer) From {5, 2, 8, 1, 9}
' {1, 2, 5, 8, 9}
Console.WriteLine(sorted.Min)
Console.WriteLine(sorted.Max)Queue and Stack
Queue is FIFO (first-in-first-out) — use for task scheduling, BFS. Stack is LIFO (last-in-first-out) — use for undo, expression evaluation, DFS. Both have O(1) enqueue/dequeue and push/pop. Construct a Stack from an array to reverse it in one line. Peek looks at the next element without removing it.
' Queue(Of T) — FIFO
Dim queue As New Queue(Of String)()
queue.Enqueue("first")
queue.Enqueue("second")
queue.Enqueue("third")
Dim next1 = queue.Dequeue() ' "first"
Dim peek = queue.Peek() ' "second" (doesn't remove)
Console.WriteLine($"Count: {queue.Count}")
' process queue
Do While queue.Count > 0
Dim item = queue.Dequeue()
Console.WriteLine(item)
Loop
' Stack(Of T) — LIFO
Dim stack As New Stack(Of Integer)()
stack.Push(1)
stack.Push(2)
stack.Push(3)
Dim top = stack.Pop() ' 3
Dim peek = stack.Peek() ' 2
' reverse using stack
Dim arr = {1, 2, 3, 4, 5}
Dim revStack As New Stack(Of Integer)(arr)
Dim reversed = revStack.ToArray() ' {5, 4, 3, 2, 1}Concurrent collections
Concurrent collections are thread-safe — use them instead of locking regular collections. ConcurrentDictionary's AddOrUpdate and GetOrAdd are atomic. ConcurrentQueue/Stack are lock-free. ConcurrentBag is fastest when each thread mostly produces and consumes its own items. BlockingCollection is the standard pattern for producer-consumer queues.
Imports System.Collections.Concurrent
' thread-safe collections for parallel programming
' ConcurrentDictionary
Dim cd As New ConcurrentDictionary(Of String, Integer)()
cd("a") = 1
cd.AddOrUpdate("a", 1, Function(key, oldVal) oldVal + 1)
cd.GetOrAdd("b", Function(key) 42)
' ConcurrentQueue (lock-free FIFO)
Dim cq As New ConcurrentQueue(Of Integer)()
cq.Enqueue(1)
Dim val As Integer
If cq.TryDequeue(val) Then
Console.WriteLine(val)
End If
' ConcurrentStack (lock-free LIFO)
Dim cs As New ConcurrentStack(Of Integer)()
cs.Push(1)
cs.TryPop(val)
' ConcurrentBag (unordered, fast for same-thread adds)
Dim cb As New ConcurrentBag(Of Integer)()
cb.Add(1)
cb.TryTake(val)
' BlockingCollection (producer-consumer)
Dim bc As New BlockingCollection(Of Integer)(100) ' bounded
' Producer:
Task.Run(Sub()
For i = 1 To 100
bc.Add(i)
Next
bc.CompleteAdding()
End Sub)
' Consumer:
Task.Run(Sub()
For Each item In bc.GetConsumingEnumerable()
Console.WriteLine(item)
Next
End Sub)File I/O Advanced
Stream reading and writing
Always wrap streams in Using blocks to ensure disposal (closes the file even on exceptions). File.ReadAllText/ReadAllLines are convenient for small files. For large files, use StreamReader line-by-line. Async I/O (ReadToEndAsync) keeps UIs responsive — never call synchronous file I/O on the UI thread.
Imports System.IO
' StreamReader
Using reader As New StreamReader("input.txt")
Dim line As String
Do While reader.Peek() >= 0
line = reader.ReadLine()
Console.WriteLine(line)
Loop
End Using
' read all at once
Dim allText = File.ReadAllText("input.txt")
Dim allLines = File.ReadAllLines("input.txt")
' StreamWriter
Using writer As New StreamWriter("output.txt")
writer.WriteLine("First line")
writer.WriteLine("Second line")
writer.Write("No newline")
End Using
' append
File.AppendAllText("log.txt", $"{Date.Now}: message{Environment.NewLine}")
' async I/O (don't block UI)
Async Function ReadAsync(path As String) As Task(Of String)
Using reader As New StreamReader(path)
Return Await reader.ReadToEndAsync()
End Using
End Function
' write async
Using writer As New StreamWriter("out.txt")
Await writer.WriteLineAsync("async line")
End UsingBinary and CSV
BinaryWriter/Reader handle typed binary data — ReadInt32/ReadDouble must match the write order exactly. TextFieldParser (in Microsoft.VisualBasic.FileIO) is the robust way to parse CSV — handles quoted fields with embedded commas and newlines. Don't roll your own CSV parser with Split(","). For complex CSV, consider the CsvHelper NuGet package.
Imports System.IO
Imports Microsoft.VisualBasic.FileIO
' BinaryWriter/Reader
Using fs As New FileStream("data.bin", FileMode.Create),
bw As New BinaryWriter(fs)
bw.Write(42I) ' Integer
bw.Write(3.14R) ' Double
bw.Write("hello") ' String (length-prefixed)
End Using
Using fs As New FileStream("data.bin", FileMode.Open),
br As New BinaryReader(fs)
Dim n = br.ReadInt32()
Dim d = br.ReadDouble()
Dim s = br.ReadString()
End Using
' CSV with TextFieldParser (handles quoted fields)
Using parser As New TextFieldParser("data.csv")
parser.TextFieldType = FieldType.Delimited
parser.SetDelimiters(",")
parser.HasFieldsEnclosedInQuotes = True
' skip header
If Not parser.EndOfData Then parser.ReadFields()
While Not parser.EndOfData
Dim fields = parser.ReadFields()
Console.WriteLine($"Name: {fields(0)}, Age: {fields(1)}")
End While
End Using
' write CSV
Dim lines = {"name,age", "Alice,30", "Bob,25"}
File.WriteAllLines("out.csv", lines)Path operations
Always use Path.Combine (not string concatenation) to join paths — it handles separators correctly. Path.GetInvalidFileNameChars helps sanitize user input for filenames. FileInfo/DirectoryInfo cache file metadata — use them when you need multiple properties. SearchOption.AllDirectories recurses; for huge trees, use EnumerateFiles (streaming).
Imports System.IO
' path manipulation (cross-platform)
Dim full = Path.Combine("C:", "Users", "alice", "data.txt")
' C:Usersalicedata.txt
Dim dir = Path.GetDirectoryName(full) ' C:Usersalice
Dim file = Path.GetFileName(full) ' data.txt
Dim name = Path.GetFileNameWithoutExtension(full) ' data
Dim ext = Path.GetExtension(full) ' .txt
' temp files
Dim tempFile = Path.GetTempFileName() ' creates empty file
Dim tempDir = Path.GetTempPath()
' check extension
If Path.GetExtension(path).Equals(".csv", StringComparison.OrdinalIgnoreCase) Then
' process CSV
End If
' directory operations
Directory.CreateDirectory("path/to/dir")
If Directory.Exists("path") Then
Dim files = Directory.GetFiles("path", "*.txt", SearchOption.AllDirectories)
Dim dirs = Directory.GetDirectories("path")
End If
' file info
Dim fi As New FileInfo("data.txt")
Console.WriteLine($"Size: {fi.Length}, Modified: {fi.LastWriteTime}")
' safe file name
Dim invalid = Path.GetInvalidFileNameChars()
Dim safe = New String(name.Select(Function(c) If(invalid.Contains(c), "_"c, c)).ToArray())File watching
FileSystemWatcher watches a directory for changes. Set NotifyFilter to limit which changes fire events (performance). The biggest gotcha: editors often fire multiple Changed events per save (write, flush, metadata) — debounce by tracking the last event time. Errors (like buffer overflow from too many events) need handling too.
Imports System.IO
' FileSystemWatcher monitors directory changes
Dim watcher As New FileSystemWatcher() With {
.Path = "C:Watch",
.Filter = "*.txt",
.IncludeSubdirectories = True,
.NotifyFilter = NotifyFilters.FileName Or NotifyFilters.LastWrite Or NotifyFilters.Size
}
' event handlers
AddHandler watcher.Created, Sub(s, e)
Console.WriteLine($"Created: {e.FullPath}")
End Sub
AddHandler watcher.Changed, Sub(s, e)
Console.WriteLine($"Changed: {e.FullPath}")
End Sub
AddHandler watcher.Deleted, Sub(s, e)
Console.WriteLine($"Deleted: {e.FullPath}")
End Sub
AddHandler watcher.Renamed, Sub(s, e)
Console.WriteLine($"Renamed: {e.OldFullPath} -> {e.FullPath}")
End Sub
AddHandler watcher.Error, Sub(s, e)
Console.WriteLine($"Error: {e.GetException().Message}")
End Sub
watcher.EnableRaisingEvents = True
' common gotcha: multiple events fire for one save
' debounce by tracking last event time
Dim lastEvent As DateTime
AddHandler watcher.Changed, Sub(s, e)
If (DateTime.Now - lastEvent).TotalMilliseconds > 500 Then
lastEvent = DateTime.Now
' process
End If
End SubSerialization
System.Text.Json is the modern, high-performance JSON serializer (replaces Newtonsoft.Json for new code). XmlSerializer handles XML — note it requires a parameterless constructor and public properties. For collections, serialize List(Of T) directly. JsonSerializerOptions with WriteIndented produces human-readable output.
Imports System.IO
Imports System.Xml.Serialization
Imports System.Text.Json
' class to serialize
Public Class Person
Public Property Name As String
Public Property Age As Integer
Public Property Email As String
End Class
' JSON (System.Text.Json — modern, fast)
Dim p As New Person With {.Name = "Alice", .Age = 30, .Email = "[email protected]"}
Dim json = JsonSerializer.Serialize(p)
File.WriteAllText("p.json", json)
Dim jsonOpts As New JsonSerializerOptions With {.WriteIndented = True}
File.WriteAllText("p_pretty.json", JsonSerializer.Serialize(p, jsonOpts))
Dim loaded = JsonSerializer.Deserialize(Of Person)(File.ReadAllText("p.json"))
' XML
Dim xs As New XmlSerializer(GetType(Person))
Using fs As New FileStream("p.xml", FileMode.Create)
xs.Serialize(fs, p)
End Using
Using fs As New FileStream("p.xml", FileMode.Open)
Dim loadedXml = DirectCast(xs.Deserialize(fs), Person)
End Using
' collections
Dim people As New List(Of Person) From {p, New Person With {.Name = "Bob"}}
Dim jsonList = JsonSerializer.Serialize(people)
Dim loadedList = JsonSerializer.Deserialize(Of List(Of Person))(jsonList)Error Handling Deep
Try/Catch patterns
Catch specific exceptions before general ones (order matters). Use Finally for cleanup — it runs even if you return or throw inside Try. When filters (VB 14+) let you catch based on exception properties. When wrapping exceptions, pass the original as innerException to preserve the stack trace. Avoid catching Exception unless you re-throw.
Try
Dim result = 10 / x
File.WriteAllText("out.txt", result.ToString())
Catch ex As DivideByZeroException
Console.WriteLine($"Cannot divide by zero: {ex.Message}")
Catch ex As IOException
Console.WriteLine($"File error: {ex.Message}")
Catch ex As UnauthorizedAccessException
Console.WriteLine($"Permission denied: {ex.Message}")
Catch ex As Exception
' catch-all (use sparingly)
Console.WriteLine($"Unexpected: {ex.Message}")
Throw ' re-throw
Finally
' always runs (even on exception or return)
' cleanup: close files, release resources
End Try
' exception filters (VB 14+)
Try
ProcessData()
Catch ex As InvalidOperationException When ex.Message.Contains("collection")
Console.WriteLine("Collection was modified")
Catch ex As InvalidOperationException
Console.WriteLine("Other invalid op")
End Try
' nested try
Try
Try
RiskyOperation()
Catch ex As Exception
Throw New InvalidOperationException("Inner failed", ex) ' wrap
End Try
Catch ex As Exception
Console.WriteLine(ex.InnerException?.Message)
End TryCustom exceptions
Custom exceptions should inherit from Exception (or a domain-specific base) and provide multiple constructors: message-only, message + inner, and domain-specific. Mark them <Serializable> if they might cross AppDomain boundaries. Add properties for context that callers need. Override ToString for clear logging.
' inherit from Exception (or a more specific base)
<Serializable>
Public Class ValidationException
Inherits Exception
Public Property FieldName As String
Public Sub New(message As String)
MyBase.New(message)
End Sub
Public Sub New(message As String, inner As Exception)
MyBase.New(message, inner)
End Sub
Public Sub New(fieldName As String, message As String)
MyBase.New(message)
Me.FieldName = fieldName
End Sub
Public Overrides Function ToString() As String
Return $"Validation error in '{FieldName}': {Message}"
End Function
End Class
' usage
Sub ValidateAge(age As Integer)
If age < 0 Then
Throw New ValidationException("age", "Age cannot be negative")
ElseIf age > 150
Throw New ValidationException("age", "Age seems unrealistic")
End If
End Sub
' catch custom
Try
ValidateAge(-5)
Catch ex As ValidationException
Console.WriteLine($"Field '{ex.FieldName}': {ex.Message}")
End TryException handling strategies
Never swallow exceptions silently — at minimum, log them. Use Using for IDisposable resources (cleaner than try/finally). Register global handlers for AppDomain.UnhandledException (last-chance) and Application.ThreadException (WinForms UI thread). AggregateException wraps multiple exceptions from parallel code — flatten and handle each InnerException.
' 1. Don't catch what you can't handle
' BAD: swallows errors silently
Try
SaveData()
Catch ex As Exception
' nothing
End Try
' GOOD: log and rethrow, or handle meaningfully
Try
SaveData()
Catch ex As IOException
Logger.Warn(ex, "Save failed, will retry")
RetrySave()
Catch ex As Exception
Logger.Error(ex, "Unexpected error saving")
Throw
End Try
' 2. Use Using instead of try/finally for IDisposable
Using fs As New FileStream("f.txt", FileMode.Open)
' use fs
End Using ' automatically disposes
' 3. Global exception handlers
AddHandler AppDomain.CurrentDomain.UnhandledException, Sub(s, e)
Logger.Fatal(DirectCast(e.ExceptionObject, Exception), "App crash")
End Sub
AddHandler Application.ThreadException, Sub(s, e)
Logger.Error(e.Exception, "UI thread error")
End Sub
' 4. AggregateException (parallel)
Try
Parallel.ForEach(items, Sub(item) ProcessItem(item))
Catch ex As AggregateException
For Each inner In ex.InnerExceptions
Logger.Error(inner, "Task failed")
Next
End TryResult patterns (functional)
The Result pattern returns success/failure as a value rather than throwing — useful for expected failures (parsing, validation) where exceptions would be expensive and noisy. Combine with extension methods (OnSuccess, OnFailure) for chaining. Use exceptions for truly exceptional cases; Result for predictable failures.
' Alternative to exceptions for expected failures
Public Class Result(Of T)
Public ReadOnly Property Value As T
Public ReadOnly Property [Error] As String
Public ReadOnly Property IsSuccess As Boolean
Private Sub New(value As T, [error] As String, isSuccess As Boolean)
Me.Value = value
Me.Error = [error]
Me.IsSuccess = isSuccess
End Sub
Public Shared Function Ok(value As T) As Result(Of T)
Return New Result(Of T)(value, Nothing, True)
End Function
Public Shared Function Fail([error] As String) As Result(Of T)
Return New Result(Of T)(Nothing, [error], False)
End Function
End Class
' usage
Function ParseInt(s As String) As Result(Of Integer)
Dim n As Integer
If Integer.TryParse(s, n) Then
Return Result(Of Integer).Ok(n)
Else
Return Result(Of Integer).Fail($"Cannot parse '{s}'")
End If
End Function
' caller
Dim result = ParseInt("42")
If result.IsSuccess Then
Console.WriteLine($"Got: {result.Value}")
Else
Console.WriteLine($"Error: {result.Error}")
End If
' chain (extension methods)
' result.OnSuccess(Function(x) x * 2).OnFailure(Function(e) 0)Logging integration
Use a logging framework (NLog, Serilog, log4net) — not Console.WriteLine. Log levels (Trace/Debug/Info/Warn/Error/Fatal) let you filter at runtime via config. Structured logging (Serilog) preserves field names — better for searching in tools like ELK or Seq. Always include context (user IDs, operation names) in log messages.
Imports NLog ' or log4net, Serilog
' configure (in app.config or code)
' NLog.config:
' <targets><target name="file" type="File" fileName="app.log" /></targets>
' <rules><logger name="*" minlevel="Info" writeTo="file" /></rules>
Public Class MyService
Private Shared ReadOnly Logger As Logger = LogManager.GetCurrentClassLogger()
Public Sub ProcessData(data As String)
Logger.Info($"Processing: {data}")
Try
Logger.Debug("Starting parse")
Dim result = Parse(data)
Logger.Info($"Done: {result}")
Catch ex As FormatException
Logger.Warn(ex, "Parse failed")
Throw
Catch ex As Exception
Logger.Error(ex, "Unexpected error")
Throw
End Try
End Sub
End Class
' structured logging (Serilog)
' Log.Information("User {UserId} logged in from {IP}", userId, ip)
' log levels (in order):
' Trace < Debug < Info < Warn < Error < Fatal
' set minimum level in config to filter
' exception with context
Try
SaveUser(user)
Catch ex As Exception
Logger.Error(ex, "Failed to save user {UserId}", user.Id)
Throw
End TryADO.NET Database
Connection and commands
Always use parameters (never string concatenation) to prevent SQL injection. Using blocks ensure connections are closed even on exceptions. ExecuteReader for SELECT; ExecuteNonQuery for INSERT/UPDATE/DELETE; ExecuteScalar for a single value (like COUNT or SCOPE_IDENTITY). Connection pooling is automatic — open late, close early.
Imports System.Data.SqlClient ' or Microsoft.Data.SqlClient for .NET+
' or MySql.Data.MySqlClient, System.Data.SQLite, etc.
Dim connStr As String = "Server=localhost;Database=mydb;Integrated Security=True;"
' basic query
Using conn As New SqlConnection(connStr)
conn.Open()
Using cmd As New SqlCommand("SELECT Id, Name FROM Users WHERE Age > @age", conn)
cmd.Parameters.AddWithValue("@age", 18)
Using reader = cmd.ExecuteReader()
While reader.Read()
Console.WriteLine($"{reader("Id")}: {reader("Name")}")
End While
End Using
End Using
End Using
' insert with parameters
Using conn As New SqlConnection(connStr), cmd As New SqlCommand(
"INSERT INTO Users (Name, Age) VALUES (@name, @age); SELECT SCOPE_IDENTITY();", conn)
cmd.Parameters.AddWithValue("@name", "Alice")
cmd.Parameters.AddWithValue("@age", 30)
conn.Open()
Dim newId = CInt(cmd.ExecuteScalar())
End Using
' update/delete
Using cmd As New SqlCommand("UPDATE Users SET Age = @age WHERE Id = @id", conn)
cmd.Parameters.AddWithValue("@age", 31)
cmd.Parameters.AddWithValue("@id", newId)
Dim rowsAffected = cmd.ExecuteNonQuery()
End UsingTransactions
Transactions ensure atomicity — all operations succeed or all fail. BeginTransaction on a single connection is for one database. TransactionScope handles distributed transactions across multiple connections (and even multiple databases) — call Complete to commit. Choose isolation level carefully: Serializable is safest but slowest; ReadCommitted is the default.
Dim connStr = "Server=localhost;Database=mydb;Integrated Security=True;"
' basic transaction
Using conn As New SqlConnection(connStr)
conn.Open()
Using tran = conn.BeginTransaction()
Try
Using cmd As New SqlCommand("", conn, tran)
cmd.CommandText = "UPDATE Accounts SET Balance = Balance - 100 WHERE Id = 1"
cmd.ExecuteNonQuery()
cmd.CommandText = "UPDATE Accounts SET Balance = Balance + 100 WHERE Id = 2"
cmd.ExecuteNonQuery()
tran.Commit()
End Using
Catch ex As Exception
tran.Rollback()
Console.WriteLine($"Transaction rolled back: {ex.Message}")
Throw
End Try
End Using
End Using
' TransactionScope (distributed transactions)
Using scope As New TransactionScope()
Using conn1 As New SqlConnection(connStr1)
' work on db1
End Using
Using conn2 As New SqlConnection(connStr2)
' work on db2
End Using
scope.Complete() ' commit all
End Using ' rollback if Complete not called
' isolation levels
Using tran = conn.BeginTransaction(IsolationLevel.Serializable)
' ...
End UsingDataTable and DataAdapter
DataTable is an in-memory table — disconnected from the database. SqlDataAdapter fills it and pushes updates back. SqlCommandBuilder auto-generates INSERT/UPDATE/DELETE commands for simple single-table scenarios. DataView filters/sorts without re-querying. For new code, prefer ORMs (EF Core, Dapper) over raw DataTables.
Dim connStr = "Server=localhost;Database=mydb;Integrated Security=True;"
' fill DataTable
Dim dt As New DataTable()
Using adapter As New SqlDataAdapter("SELECT * FROM Users", connStr)
adapter.Fill(dt)
End Using
' access rows
For Each row As DataRow In dt.Rows
Console.WriteLine($"{row("Id")}: {row("Name")}")
Next
' filter and sort (in-memory)
Dim view As New DataView(dt) With {
.Sort = "Name ASC",
.RowFilter = "Age > 25"
}
For Each row As DataRowView In view
Console.WriteLine(row("Name"))
Next
' modify and update
Dim newRow = dt.NewRow()
newRow("Name") = "Carol"
newRow("Age") = 28
dt.Rows.Add(newRow)
dt.Rows(0)("Age") = 31 ' modify
dt.Rows(1).Delete() ' mark for deletion
' push changes back to DB
Using adapter As New SqlDataAdapter("SELECT * FROM Users", connStr)
Dim builder As New SqlCommandBuilder(adapter)
adapter.Update(dt) ' generates INSERT/UPDATE/DELETE
End Using
' LINQ on DataTable
Dim young = From row In dt.AsEnumerable()
Where row.Field(Of Integer)("Age") < 30
Select row.Field(Of String)("Name")Dapper micro-ORM
Dapper is the sweet spot between raw ADO.NET (fast but verbose) and EF Core (productive but slower). It auto-maps columns to properties. Use anonymous objects for parameters. Query<T> for lists; QuerySingle<T> for one; Execute for non-queries. Pass a collection to Execute for batch operations. Multi-mapping handles joins.
Imports Dapper ' NuGet: Install-Package Dapper
' Dapper is a lightweight ORM that extends IDbConnection
Dim connStr = "Server=localhost;Database=mydb;Integrated Security=True;"
Using conn As New SqlConnection(connStr)
' query
Dim users = conn.Query(Of User)("SELECT * FROM Users WHERE Age > @age",
New With {Key .age = 18}).ToList()
' single
Dim user = conn.QuerySingle(Of User)("SELECT * FROM Users WHERE Id = @id",
New With {Key .id = 5})
' execute
Dim rows = conn.Execute("INSERT INTO Users (Name, Age) VALUES (@name, @age)",
New With {Key .name = "Alice", Key .age = 30})
' batch insert
Dim newUsers = {
New With {Key .name = "Bob", Key .age = 25},
New With {Key .name = "Carol", Key .age = 28}
}
conn.Execute("INSERT INTO Users (Name, Age) VALUES (@name, @age)", newUsers)
' multi-result (one-to-many)
Dim sql = "SELECT * FROM Companies; SELECT * FROM Employees;"
Using multi = conn.QueryMultiple(sql)
Dim companies = multi.Read(Of Company)().ToList()
Dim employees = multi.Read(Of Employee)().ToList()
End Using
' stored procedure
Dim result = conn.Query(Of User)("sp_GetUsers", New With {Key .role = "admin"},
commandType:=CommandType.StoredProcedure)
End UsingEF Core basics
EF Core is the full-featured ORM — handles relationships, migrations, change tracking, and LINQ queries. DbSet properties are the entry points. SaveChanges persists all tracked changes. Include eager-loads related entities (avoids N+1 queries). For read-only scenarios, use AsNoTracking() for better performance. Migrations manage schema changes.
Imports Microsoft.EntityFrameworkCore
' DbContext
Public Class AppDbContext
Inherits DbContext
Public Property Users As DbSet(Of User)
Public Property Orders As DbSet(Of Order)
Protected Overrides Sub OnConfiguring(options As DbContextOptionsBuilder)
options.UseSqlServer("Server=localhost;Database=mydb;Integrated Security=True;")
End Sub
Protected Overrides Sub OnModelCreating(modelBuilder As ModelBuilder)
modelBuilder.Entity(Of User)().
HasKey(Function(u) u.Id).
HasMany(Function(u) u.Orders).
WithOne(Function(o) o.User).
HasForeignKey(Function(o) o.UserId)
End Sub
End Class
' CRUD
Using db As New AppDbContext()
' create
db.Users.Add(New User With {.Name = "Alice", .Age = 30})
db.SaveChanges()
' read
Dim alice = db.Users.First(Function(u) u.Name = "Alice")
Dim adults = db.Users.Where(Function(u) u.Age >= 18).ToList()
' update
alice.Age = 31
db.SaveChanges()
' delete
db.Users.Remove(alice)
db.SaveChanges()
' eager loading
Dim usersWithOrders = db.Users.Include(Function(u) u.Orders).ToList()
' raw SQL
Dim raw = db.Users.FromSqlRaw("SELECT * FROM Users WHERE Age > {0}", 18).ToList()
' transactions
Using tran = db.Database.BeginTransaction()
Try
' work
db.SaveChanges()
tran.Commit()
Catch
tran.Rollback()
Throw
End Try
End Using
End UsingLINQ in VB
Query syntax
VB's LINQ query syntax is SQL-like and often more readable for complex queries. From ... Where ... Select is the basic pattern. Multiple From clauses do cross joins (filter with Where). Group By creates groups accessible via the Group keyword. Join requires the Equals keyword (not On ... Equals ... like SQL).
Dim nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
' query syntax (SQL-like)
Dim evens = From n In nums
Where n Mod 2 = 0
Select n
' List: 2, 4, 6, 8, 10
Dim squares = From n In nums
Select n, Square = n * n
Dim pairs = From n In nums
Where n > 3
Order By n Descending
Select n
' with multiple sources
Dim names = {"Alice", "Bob", "Carol"}
Dim ages = {30, 25, 42}
Dim people = From name In names
From age In ages
Where age > 26
Select name, age
' group by
Dim orders = GetOrders()
Dim byCustomer = From o In orders
Group o By o.CustomerId Into Group
Select CustomerId, Total = Group.Sum(Function(o) o.Amount)
' join
Dim result = From u In users
Join o In orders On u.Id Equals o.UserId
Select u.Name, o.AmountMethod syntax
Method syntax uses extension methods and lambdas — more compact and composable than query syntax. Function(x) ... is the lambda syntax in VB. First throws if empty; FirstOrDefault returns default (Nothing for reference types, 0 for Integer). Skip/Take is the standard pagination pattern. Both syntaxes compile to the same IL.
Dim nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
' method syntax (fluent)
Dim evens = nums.Where(Function(n) n Mod 2 = 0).ToList()
Dim squares = nums.Select(Function(n) n * n).ToList()
Dim sorted = nums.OrderBy(Function(n) n).ToList()
Dim desc = nums.OrderByDescending(Function(n) n).ToList()
' aggregation
Dim sum = nums.Sum()
Dim avg = nums.Average()
Dim min = nums.Min()
Dim max = nums.Max()
Dim count = nums.Count()
Dim countEven = nums.Count(Function(n) n Mod 2 = 0)
' element access
Dim first = nums.First()
Dim firstEven = nums.First(Function(n) n Mod 2 = 0)
Dim maybeFirst = nums.FirstOrDefault(Function(n) n > 100) ' 0 if not found
Dim single = nums.Single(Function(n) n = 5)
' take/skip
Dim top3 = nums.Take(3).ToList()
Dim skip3 = nums.Skip(3).ToList()
Dim page = nums.Skip(10).Take(5).ToList() ' pagination
' distinct
Dim unique = nums.Distinct().ToList()
Dim union = nums.Union({11, 12}).ToList()
Dim intersect = nums.Intersect({5, 6, 7}).ToList()
Dim except = nums.Except({5, 6, 7}).ToList()Grouping and aggregation
GroupBy returns IGrouping(Of Key, Element) — each group has a Key and is itself enumerable. ToLookup creates a lookup table (like a multi-valued dictionary). Anonymous types (New With {Key .X = ...}) are great for projections — Key makes the property immutable and part of equality. Aggregate is the most flexible (but least readable) combiner.
Public Class Order
Public Property Id As Integer
Public Property CustomerId As Integer
Public Property Amount As Decimal
Public Property Date As DateTime
End Class
Dim orders = GetOrders()
' group by
Dim byCustomer = orders.GroupBy(Function(o) o.CustomerId)
For Each grp In byCustomer
Console.WriteLine($"Customer {grp.Key}: {grp.Count()} orders, total {grp.Sum(Function(o) o.Amount)}")
Next
' group with projection
Dim summary = orders.GroupBy(Function(o) o.CustomerId).
Select(Function(g) New With {
Key .CustomerId = g.Key,
Key .OrderCount = g.Count(),
Key .TotalAmount = g.Sum(Function(o) o.Amount),
Key .AvgAmount = g.Average(Function(o) o.Amount),
Key .MaxAmount = g.Max(Function(o) o.Amount)
}).ToList()
' multi-level grouping
Dim byMonth = orders.GroupBy(Function(o) New With {Key o.Date.Year, Key o.Date.Month})
For Each grp In byMonth
Console.WriteLine($"{grp.Key.Year}-{grp.Key.Month}: {grp.Sum(Function(o) o.Amount)}")
Next
' lookup (like a dictionary of lists)
Dim lookup = orders.ToLookup(Function(o) o.CustomerId)
Dim customerOrders = lookup(5).ToList() ' all orders for customer 5
' aggregate with seed
Dim total = orders.Aggregate(0D, Function(acc, o) acc + o.Amount)Joining data
Join is inner join (only matching). Group Join with DefaultIfEmpty gives left join. Multiple Join clauses chain naturally. The method-syntax Join takes four lambdas: outer key, inner key, result selector. Zip pairs elements by position — useful but rare. For complex joins, query syntax is usually clearer than method syntax.
Public Class User
Public Property Id As Integer
Public Property Name As String
Public Property DepartmentId As Integer
End Class
Public Class Department
Public Property Id As Integer
Public Property Name As String
End Class
Dim users = GetUsers()
Dim departments = GetDepartments()
' inner join
Dim result = From u In users
Join d In departments On u.DepartmentId Equals d.Id
Select u.Name, Department = d.Name
' method syntax
Dim result2 = users.Join(departments,
Function(u) u.DepartmentId,
Function(d) d.Id,
Function(u, d) New With {Key .Name = u.Name, Key .Dept = d.Name}).ToList()
' group join (left join)
Dim leftJoin = From d In departments
Group Join u In users On d.Id Equals u.DepartmentId Into Group
From u In Group.DefaultIfEmpty()
Select Department = d.Name, User = If(u Is Nothing, "(none)", u.Name)
' multiple joins
Dim detail = From u In users
Join d In departments On u.DepartmentId Equals d.Id
Join o In orders On u.Id Equals o.UserId
Select u.Name, d.Name, o.Amount
' zip (pair two sequences element-by-element)
Dim nums = {1, 2, 3}
Dim words = {"one", "two", "three"}
Dim pairs = nums.Zip(words, Function(n, w) $"{n}={w}").ToList()
' {"1=one", "2=two", "3=three"}Deferred execution
LINQ uses deferred execution — queries run when iterated, not when defined. This means each For Each re-runs the query. To cache results, call ToList/ToArray. Avoid side effects in query lambdas (they run unpredictably). Some operators (Count, First, Any) force immediate execution. Any/All short-circuit.
Dim nums = New List(Of Integer) From {1, 2, 3, 4, 5}
' query is NOT executed yet
Dim query = nums.Where(Function(n) n > 2).Select(Function(n) n * 10)
' executes when iterated
For Each n In query
Console.WriteLine(n) ' 30, 40, 50
Next
' executes AGAIN on each iteration
For Each n In query
Console.WriteLine(n) ' 30, 40, 50 again
Next
' force immediate execution
Dim list = query.ToList() ' executes once, caches
Dim arr = query.ToArray()
Dim count = query.Count() ' executes
Dim first = query.First() ' executes
' side effects in query — be careful!
Dim i = 0
Dim bad = nums.Where(Function(n)
i += 1
Return n > 2
End Function)
' i changes every time you iterate bad
' eager vs deferred operators
' Deferred: Where, Select, OrderBy, Skip, Take, Distinct
' Eager: ToList, ToArray, Count, First, Sum, Max, Any, All
' check if any/all match
Dim hasEven = nums.Any(Function(n) n Mod 2 = 0)
Dim allPositive = nums.All(Function(n) n > 0)Multithreading
Tasks and async
Task.Run schedules work on a thread pool thread. Async/Await makes async code look synchronous — the method pauses at Await without blocking the thread. Task.WhenAll waits for all; Task.WhenAny returns the first to finish. Always pass CancellationToken to long-running tasks and call ThrowIfCancellationRequested periodically.
Imports System.Threading.Tasks
' Task creation
Dim t1 = Task.Run(Function() DoWork())
Dim t2 = Task.Run(Function() 42) ' returns Integer
Dim result = Await t2 ' 42
' async function
Async Function FetchDataAsync() As Task(Of String)
Using client As New HttpClient()
Return Await client.GetStringAsync("https://api.example.com")
End Using
End Function
' call it
Dim data = Await FetchDataAsync()
' parallel execution
Dim tasks = {
Task.Run(Function() ComputePart1()),
Task.Run(Function() ComputePart2()),
Task.Run(Function() ComputePart3())
}
Dim results = Await Task.WhenAll(tasks)
' when any (first to finish)
Dim allTasks = urls.Select(Function(u) FetchAsync(u))
Dim firstDone = Await Task.WhenAny(allTasks)
Dim firstResult = Await firstDone
' continuation
Dim t = Task.Run(Function() 42).
ContinueWith(Function(prev) prev.Result * 2)
Console.WriteLine(t.Result) ' 84
' cancellation
Dim cts As New CancellationTokenSource()
Dim token = cts.Token
Dim task = Task.Run(Function()
For i = 1 To 100
token.ThrowIfCancellationRequested()
Thread.Sleep(100)
Next
End Function, token)
cts.CancelAfter(500) ' cancel after 500msParallel loops
Parallel.For/ForEach partition work across thread pool threads — great for CPU-bound loops. The thread-local overload is for reductions (sum, max) without locking on every iteration. state.Break stops higher iterations; state.Stop halts immediately. PLINQ (AsParallel) parallelizes LINQ — but only helps for CPU-heavy operations.
Imports System.Threading.Tasks
' Parallel.For
Parallel.For(0, 100, Sub(i)
ProcessItem(i)
End Sub)
' with options
Dim opts As New ParallelOptions With {
.MaxDegreeOfParallelism = Environment.ProcessorCount
}
Parallel.For(0, 100, opts, Sub(i)
ProcessItem(i)
End Sub)
' Parallel.ForEach
Dim items = GetItems()
Parallel.ForEach(items, Sub(item)
ProcessItem(item)
End Sub)
' with state (thread-local)
Dim total As Integer = 0
Dim lockObj As New Object()
Parallel.For(0, 1000,
Function() 0, ' local init
Function(i, state, localTotal)
localTotal += Compute(i)
Return localTotal
End Function, ' body
Sub(localTotal)
SyncLock lockObj
total += localTotal
End SyncLock
End Sub ' local finally
)
' break vs stop
Parallel.For(0, 100, Sub(i, state)
If i = 50 Then state.Break() ' stop iterations > 50
If i = 25 Then state.Stop() ' stop all immediately
End Sub)
' PLINQ
Dim nums = Enumerable.Range(1, 1000)
Dim squares = nums.AsParallel().
Where(Function(n) n Mod 2 = 0).
Select(Function(n) n * n).
ToList()Synchronization
SyncLock is the simplest synchronization — mutual exclusion. Interlocked is for atomic int/long ops (faster than locks). Mutex can be named and shared across processes (single-instance apps). Semaphore limits concurrency (e.g., max 3 connections). ReaderWriterLockSlim allows multiple readers OR one writer — great for read-heavy caches.
Imports System.Threading
' SyncLock (Monitor.Enter/Exit)
Private ReadOnly lockObj As New Object()
Private counter As Integer = 0
Sub Increment()
SyncLock lockObj
counter += 1
End SyncLock
End Sub
' Interlocked (atomic operations)
Interlocked.Increment(counter)
Interlocked.Decrement(counter)
Interlocked.Add(counter, 10)
Interlocked.Exchange(counter, 0)
Dim oldVal = Interlocked.CompareExchange(counter, 42, 0) ' if 0, set to 42
' Mutex (cross-process)
Using mtx As New Mutex(False, "Global\MyAppMutex")
If mtx.WaitOne(0) Then
' got the mutex — only one instance runs
Else
' another instance is running
End If
End Using
' Semaphore (limit concurrent access)
Dim sem As New Semaphore(3, 3) ' 3 concurrent
sem.WaitOne()
Try
' work
Finally
sem.Release()
End Try
' ReaderWriterLockSlim
Dim rwLock As New ReaderWriterLockSlim()
rwLock.EnterReadLock()
Try
' multiple readers
Finally
rwLock.ExitReadLock()
End Try
rwLock.EnterWriteLock()
Try
' exclusive write
Finally
rwLock.ExitWriteLock()
End TryChannels and producer-consumer
Channel<T> (modern) is the recommended way to build producer-consumer pipelines — fully async, supports backpressure via bounded capacity. ForEachAsync (parallel consumption) lets multiple consumers read concurrently. BlockingCollection is the older synchronous equivalent. ConcurrentQueue/Stack are lock-free collections for direct use.
Imports System.Threading.Channels
' create a channel (bounded for backpressure)
Dim channel = Channel.CreateBounded(Of Integer)(100)
' producer
Async Function ProducerAsync() As Task
For i = 1 To 1000
Await channel.Writer.WriteAsync(i)
Next
channel.Writer.Complete()
End Function
' consumer
Async Function ConsumerAsync() As Task
Await ForEachAsync(channel.Reader.ReadAllAsync(),
Environment.ProcessorCount,
Async Function(item)
Await ProcessItemAsync(item)
End Function)
End Function
' multiple producers/consumers
Dim producers = Enumerable.Range(0, 3).Select(Function(i) ProducerAsync())
Dim consumers = Enumerable.Range(0, 3).Select(Function(i) ConsumerAsync())
Await Task.WhenAll(producers.Concat(consumers))
' BlockingCollection (older API, still useful)
Dim bc As New BlockingCollection(Of Integer)(100)
' producer
Task.Run(Sub()
For i = 1 To 100
bc.Add(i)
Next
bc.CompleteAdding()
End Sub)
' consumer
Task.Run(Sub()
For Each item In bc.GetConsumingEnumerable()
ProcessItem(item)
Next
End Sub)
' thread-safe queue
Dim queue As New ConcurrentQueue(Of Integer)()
queue.Enqueue(1)
Dim val As Integer
queue.TryDequeue(val)Timers and scheduling
Three timer types: System.Threading.Timer (thread pool, most efficient), System.Timers.Timer (server scenarios, events), Windows.Forms.Timer (UI thread, for UI updates). For async code, Task.Delay is cleaner than timers. Always dispose timers to prevent leaks. For periodic async work, a While loop with Task.Delay and a CancellationToken is the cleanest pattern.
Imports System.Threading
' System.Threading.Timer (background thread)
Dim timer As New Timer(Sub(state)
Console.WriteLine($"Tick at {DateTime.Now}")
End Sub, Nothing, TimeSpan.Zero, TimeSpan.FromSeconds(5))
' change interval
timer.Change(TimeSpan.Zero, TimeSpan.FromSeconds(10))
' dispose
timer.Dispose()
' System.Timers.Timer (events, can use SynchronizingObject)
Dim eventTimer As New Timers.Timer With {
.Interval = 5000,
.AutoReset = True
}
AddHandler eventTimer.Elapsed, Sub(s, e)
Console.WriteLine("Elapsed")
End Sub
eventTimer.Start()
' UI timer (WinForms — runs on UI thread)
Dim uiTimer As New Windows.Forms.Timer With {.Interval = 1000}
AddHandler uiTimer.Tick, Sub(s, e)
Label1.Text = DateTime.Now.ToString()
End Sub
uiTimer.Start()
' one-shot delay
Async Function DoLaterAsync() As Task
Await Task.Delay(5000) ' 5 seconds
Console.WriteLine("Done waiting")
End Function
' periodic with cancellation
Async Function PeriodicAsync(token As CancellationToken) As Task
While Not token.IsCancellationRequested
DoWork()
Await Task.Delay(1000, token)
End While
End FunctionSerialization
JSON with System.Text.Json
System.Text.Json is the modern, fast JSON serializer built into .NET. Attributes control serialization: JsonPropertyName renames, JsonIgnore excludes. JsonNamingPolicy.CamelCase matches JavaScript conventions. For huge JSON, serialize to a stream (not a string) to avoid loading everything into memory. JsonDocument parses without a target type.
Imports System.Text.Json
Imports System.Text.Json.Serialization
Public Class Person
Public Property Id As Integer
Public Property Name As String
<JsonPropertyName("email")>
Public Property Email As String
<JsonIgnore>
Public Property Password As String
Public Property CreatedAt As DateTime
End Class
' serialize
Dim p As New Person With {.Id = 1, .Name = "Alice", .Email = "[email protected]", .CreatedAt = Date.Now}
Dim json As String = JsonSerializer.Serialize(p)
' with options
Dim opts As New JsonSerializerOptions With {
.WriteIndented = True,
.PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
}
json = JsonSerializer.Serialize(p, opts)
' deserialize
Dim loaded = JsonSerializer.Deserialize(Of Person)(json)
' collections
Dim people As New List(Of Person) From {p}
Dim jsonList = JsonSerializer.Serialize(people)
Dim loadedList = JsonSerializer.Deserialize(Of List(Of Person))(jsonList)
' dynamic (JsonDocument)
Using doc As JsonDocument = JsonDocument.Parse(json)
Dim name = doc.RootElement.GetProperty("name").GetString()
Console.WriteLine(name)
End Using
' stream (large files)
Using fs As New FileStream("data.json", FileMode.Create)
JsonSerializer.Serialize(fs, p)
End UsingXML serialization
XmlSerializer is verbose but flexible. Attributes control element/attribute names and structure. It requires a parameterless constructor and public read/write properties. XmlArray/XmlArrayItem control nested collections. For reading large XML files, prefer XmlReader (streaming) over XmlSerializer (loads everything into memory).
Imports System.Xml.Serialization
Imports System.IO
<XmlRoot("person")>
Public Class Person
<XmlElement("id")>
Public Property Id As Integer
<XmlElement("name")>
Public Property Name As String
<XmlAttribute("role")>
Public Property Role As String
<XmlIgnore>
Public Property Password As String
<XmlArray("orders")>
<XmlArrayItem("order")>
Public Property Orders As List(Of Order)
End Class
' serialize
Dim xs As New XmlSerializer(GetType(Person))
Using fs As New FileStream("p.xml", FileMode.Create)
xs.Serialize(fs, p)
End Using
' with settings
Dim settings As New XmlWriterSettings With {
.Indent = True,
.IndentChars = " ",
.Encoding = Encoding.UTF8
}
Using fs As New FileStream("p.xml", FileMode.Create),
xw As XmlWriter = XmlWriter.Create(fs, settings)
xs.Serialize(xw, p)
End Using
' deserialize
Using fs As New FileStream("p.xml", FileMode.Open)
Dim loaded = DirectCast(xs.Deserialize(fs), Person)
End Using
' collections
Dim xsList As New XmlSerializer(GetType(List(Of Person)))
Using fs As New FileStream("people.xml", FileMode.Create)
xsList.Serialize(fs, people)
End UsingCSV
TextFieldParser handles quoted CSV fields correctly. For writing, escape fields containing commas, quotes, or newlines by wrapping in quotes and doubling internal quotes. For production, use CsvHelper (NuGet) — handles all edge cases, supports custom mapping, and is much faster. Always use a library for CSV; the format has surprising edge cases.
Imports Microsoft.VisualBasic.FileIO
' read CSV (handles quoted fields)
Using parser As New TextFieldParser("data.csv")
parser.TextFieldType = FieldType.Delimited
parser.SetDelimiters(",")
parser.HasFieldsEnclosedInQuotes = True
While Not parser.EndOfData
Dim fields = parser.ReadFields()
Console.WriteLine($"{fields(0)}, {fields(1)}")
End While
End Using
' write CSV (manual, with proper escaping)
Function CsvEscape(s As String) As String
If s.Contains(",") OrElse s.Contains("""") OrElse s.Contains(vbCrLf) Then
Return $"""{s.Replace("""", """""")}"""
End If
Return s
End Function
Dim lines As New List(Of String)
lines.Add("name,age,email")
For Each p In people
lines.Add($"{CsvEscape(p.Name)},{p.Age},{CsvEscape(p.Email)}")
Next
File.WriteAllLines("out.csv", lines)
' CsvHelper (NuGet) — production-grade
' Install-Package CsvHelper
Imports CsvHelper
Using reader As New StreamReader("data.csv"),
csv As New CsvReader(reader, CultureInfo.InvariantCulture)
Dim records = csv.GetRecords(Of Person)().ToList()
End Using
Using writer As New StreamWriter("out.csv"),
csv As New CsvWriter(writer, CultureInfo.InvariantCulture)
csv.WriteRecords(people)
End UsingBinary and DataContract
DataContractSerializer is more version-tolerant than XmlSerializer (handles added/removed fields via Order). For high-performance binary serialization, use Protocol Buffers (protobuf-net) or MessagePack — both are 10-100x smaller and faster than XML/JSON. Choose based on needs: human-readable (JSON/XML) vs compact/fast (protobuf/messagepack).
Imports System.Runtime.Serialization
Imports System.IO
' DataContract (version-tolerant)
<DataContract>
Public Class Person
<DataMember(Order:=1)>
Public Property Id As Integer
<DataMember(Order:=2)>
Public Property Name As String
<DataMember(Order:=3, IsRequired:=False)>
Public Property Email As String
End Class
' serialize to XML
Dim ser As New DataContractSerializer(GetType(Person))
Using fs As New FileStream("p.xml", FileMode.Create)
ser.WriteObject(fs, p)
End Using
' deserialize
Using fs As New FileStream("p.xml", FileMode.Open)
Dim loaded = DirectCast(ser.ReadObject(fs), Person)
End Using
' Protocol Buffers (protobuf-net, NuGet)
' Install-Package protobuf-net
<ProtoContract>
Public Class Person
<ProtoMember(1)>
Public Property Id As Integer
<ProtoMember(2)>
Public Property Name As String
End Class
' serialize
Using fs As New FileStream("p.bin", FileMode.Create)
ProtoBuf.Serializer.Serialize(fs, p)
End Using
' deserialize
Using fs As New FileStream("p.bin", FileMode.Open)
Dim loaded = ProtoBuf.Serializer.Deserialize(Of Person)(fs)
End Using
' MessagePack (MessagePack-CSharp, NuGet)
' compact, fast, schema-less
Dim bytes = MessagePack.MessagePackSerializer.Serialize(p)
Dim loaded = MessagePack.MessagePackSerializer.Deserialize(Of Person)(bytes)Custom converters
Custom converters handle types System.Text.Json doesn't support natively (DateOnly, TimeSpan, custom types). JsonDerivedType (NET 7+) handles polymorphism — serializes the runtime type with a discriminator. ShouldSerialize* methods conditionally include properties (like XmlSerializer's pattern). Always register converters in JsonSerializerOptions.
Imports System.Text.Json
Imports System.Text.Json.Serialization
' custom converter for a type
Public Class DateOnlyConverter
Inherits JsonConverter(Of DateOnly)
Public Overrides Function Read(reader As ByRef Utf8JsonReader,
type As Type, options As JsonSerializerOptions) As DateOnly
Return DateOnly.Parse(reader.GetString())
End Function
Public Overrides Sub Write(writer As Utf8JsonWriter, value As DateOnly, options As JsonSerializerOptions)
writer.WriteStringValue(value.ToString("yyyy-MM-dd"))
End Sub
End Class
' register globally
Dim opts As New JsonSerializerOptions()
opts.Converters.Add(New DateOnlyConverter())
' or per-property
Public Class Event
<JsonConverter(GetType(DateOnlyConverter))>
Public Property Date As DateOnly
End Class
' polymorphic serialization
<JsonDerivedType(GetType(Dog), "dog")>
<JsonDerivedType(GetType(Cat), "cat")>
Public MustInherit Class Animal
Public Property Name As String
End Class
Public Class Dog
Inherits Animal
Public Property Breed As String
End Class
Dim a As Animal = New Dog With {.Name = "Rex", .Breed = "Lab"}
Dim json = JsonSerializer.Serialize(a, GetType(Animal))
' {"$type":"dog","name":"Rex","breed":"Lab"}
' conditional serialization
Public Class User
Public Property Name As String
Public Property Password As String
Public Function ShouldSerializePassword() As Boolean
Return False ' never serialize password
End Function
End ClassReflection
Type inspection
Reflection lets you inspect types at runtime. GetType(Type) for compile-time types; obj.GetType() for runtime types. BindingFlags filter members (Public/NonPublic, Instance/Static). GetCustomAttributes returns attributes applied to a type. Reflection is slow — cache Type objects and MemberInfo for repeated use.
Imports System.Reflection
Dim t As Type = GetType(String)
' or: Dim t = obj.GetType()
' basic info
Console.WriteLine($"Name: {t.Name}")
Console.WriteLine($"Full: {t.FullName}")
Console.WriteLine($"Base: {t.BaseType}")
Console.WriteLine($"IsClass: {t.IsClass}")
Console.WriteLine($"IsEnum: {t.IsEnum}")
Console.WriteLine($"IsGenericType: {t.IsGenericType}")
' interfaces
For Each i In t.GetInterfaces()
Console.WriteLine($"Implements: {i.Name}")
Next
' properties
For Each prop In t.GetProperties()
Console.WriteLine($"Property: {prop.Name} ({prop.PropertyType.Name})")
Next
' methods
For Each m In t.GetMethods(BindingFlags.Public Or BindingFlags.Instance)
Console.WriteLine($"Method: {m.Name}({String.Join(", ", m.GetParameters().Select(Function(p) p.ParameterType.Name))})")
Next
' fields, constructors, events
t.GetFields()
t.GetConstructors()
t.GetEvents()
' attributes
For Each attr In t.GetCustomAttributes(False)
Console.WriteLine($"Attribute: {attr.GetType().Name}")
Next
' generic type args
If t.IsGenericType Then
For Each arg In t.GetGenericArguments()
Console.WriteLine($"Type arg: {arg.Name}")
Next
End IfCreating instances and invoking
Activator.CreateInstance creates objects by type. MethodInfo.Invoke calls methods — pass args as Object(). PropertyInfo.GetValue/SetValue access properties. BindingFlags.NonPublic lets you reach private members (use sparingly — breaks encapsulation). MakeGenericMethod constructs a generic method at runtime. This is the foundation of plugin systems.
Imports System.Reflection
' create instance
Dim t As Type = GetType(List(Of String))
Dim obj As Object = Activator.CreateInstance(t)
' with constructor args
Dim dt As DateTime = Activator.CreateInstance(GetType(DateTime), {2024, 12, 31})
' invoke method
Dim mi As MethodInfo = t.GetMethod("Add")
mi.Invoke(obj, New Object() {"hello"})
' property get/set
Dim pi As PropertyInfo = t.GetProperty("Count")
Dim count As Integer = CInt(pi.GetValue(obj))
pi.SetValue(obj, 10) ' may throw if read-only
' field get/set
Dim fi As FieldInfo = GetType(MyClass).GetField("_private", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim val = fi.GetValue(instance)
fi.SetValue(instance, newValue)
' generic method
Dim method As MethodInfo = GetType(Enumerable).GetMethod("Where")
Dim generic = method.MakeGenericMethod(GetType(Integer))
Dim filtered = generic.Invoke(Nothing, New Object() {nums, predicate})
' create delegate from method
Dim del As [Delegate] = [Delegate].CreateDelegate(GetType(Func(Of String, Boolean)), mi)
' load assembly
Dim asm As Assembly = Assembly.LoadFrom("plugin.dll")
Dim pluginType As Type = asm.GetType("MyPlugin")
Dim plugin As Object = Activator.CreateInstance(pluginType)Attributes
Custom attributes add metadata to types and members. AttributeUsage controls where they can apply and whether multiples are allowed. GetCustomAttribute/GetCustomAttributes read them at runtime via reflection. Built-in attributes: Obsolete (compiler warning/error), Conditional (compile-time inclusion), Serializable. Attributes power validation, ORM mapping, serialization, and more.
Imports System
' define custom attribute
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method, AllowMultiple:=True)>
Public Class DescriptionAttribute
Inherits Attribute
Public ReadOnly Property Text As String
Public Sub New(text As String)
Me.Text = text
End Sub
End Class
' apply
<Description("User service")>
Public Class UserService
<Description("Get user by ID")>
Public Function GetUser(id As Integer) As User
' ...
End Function
End Class
' read attributes
Dim t As Type = GetType(UserService)
Dim classAttr = CType(Attribute.GetCustomAttribute(t, GetType(DescriptionAttribute)), DescriptionAttribute)
Console.WriteLine(classAttr?.Text)
For Each mi In t.GetMethods()
Dim methodAttr = CType(Attribute.GetCustomAttribute(mi, GetType(DescriptionAttribute)), DescriptionAttribute)
If methodAttr IsNot Nothing Then
Console.WriteLine($"{mi.Name}: {methodAttr.Text}")
End If
Next
' built-in attributes
<Obsolete("Use NewMethod instead", True)> ' True = error, False = warning
Sub OldMethod()
End Sub
<Conditional("DEBUG")>
Sub LogDebug(msg As String)
Console.WriteLine(msg)
End Sub
<Serializable>
Public Class MyData
End ClassEmit and dynamic code
Reflection.Emit generates IL at runtime — powerful but low-level. DefineDynamicAssembly -> Module -> Type -> Method -> ILGenerator. Emit writes IL opcodes. Expression trees are a higher-level alternative — build expressions as data, then Compile to a delegate. Use Emit for extreme performance (custom serializers); Expression trees for dynamic queries (EF Core).
Imports System.Reflection
Imports System.Reflection.Emit
' build a dynamic assembly/module/type/method
Dim asmName As New AssemblyName("DynamicAsm")
Dim asm As AssemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run)
Dim mod As ModuleBuilder = asm.DefineDynamicModule("MainModule")
Dim type As TypeBuilder = mod.DefineType("Calculator", TypeAttributes.Public)
' add a method
Dim mb As MethodBuilder = type.DefineMethod("Add",
MethodAttributes.Public Or MethodAttributes.Static,
GetType(Integer), {GetType(Integer), GetType(Integer)})
Dim il As ILGenerator = mb.GetILGenerator()
il.Emit(OpCodes.Ldarg_0) ' load first arg
il.Emit(OpCodes.Ldarg_1) ' load second arg
il.Emit(OpCodes.Add) ' add
il.Emit(OpCodes.Ret) ' return
Dim createdType As Type = type.CreateType()
Dim result As Integer = CInt(createdType.GetMethod("Add").Invoke(Nothing, {5, 3}))
Console.WriteLine(result) ' 8
' expression trees (higher-level)
Imports System.Linq.Expressions
Dim xParam As ParameterExpression = Expression.Parameter(GetType(Integer), "x")
Dim yParam As ParameterExpression = Expression.Parameter(GetType(Integer), "y")
Dim body As BinaryExpression = Expression.Add(xParam, yParam)
Dim addFunc As Func(Of Integer, Integer, Integer) =
Expression.Lambda(Of Func(Of Integer, Integer, Integer))(body, {xParam, yParam}).Compile()
Console.WriteLine(addFunc(5, 3)) ' 8Performance considerations
Reflection is 100-1000x slower than direct calls. Always cache MemberInfo objects. For hot paths, compile to delegates via Expression trees — nearly as fast as direct calls. Source generators (modern .NET) can do reflection at compile time, eliminating runtime cost entirely. Use reflection for frameworks (serializers, ORMs, DI); avoid it in business logic.
Imports System.Reflection
' reflection is slow — cache and optimize
' BAD: re-fetch on every call
For Each item In items
Dim prop = item.GetType().GetProperty("Name")
Dim name = CStr(prop.GetValue(item))
Next
' GOOD: cache PropertyInfo
Dim propInfo As PropertyInfo = GetType(Item).GetProperty("Name")
For Each item In items
Dim name = CStr(propInfo.GetValue(item))
Next
' BEST: compile to delegate (10-100x faster)
Dim getter = CreateGetter(Of Item, String)("Name")
For Each item In items
Dim name = getter(item)
Next
Function CreateGetter(Of T, TResult)(propName As String) As Func(Of T, TResult)
Dim param = Expression.Parameter(GetType(T), "obj")
Dim body = Expression.Property(param, propName)
Return Expression.Lambda(Of Func(Of T, TResult))(body, param).Compile()
End Function
' source generators (modern alternative)
' compile-time reflection — no runtime cost
' Partial Class MyService
' <GenerateReflection>
' Public Shared Sub PrintMethods()
' ' generated code knows the types at compile time
' End Sub
' End Class
' when to use reflection:
' - serializers (JSON, XML)
' - ORMs (mapping columns to properties)
' - DI containers (constructor injection)
' - plugin systems
' - testing frameworks
' avoid for hot paths — cache or compileInterop
P/Invoke (calling Win32 DLLs)
P/Invoke lets you call native DLL functions. DllImport specifies the library and options. CharSet.Unicode handles string marshaling. ByRef for output parameters. StructLayout(Sequential) ensures structs match C layout. SetLastError=True lets you query the last Win32 error via Marshal.GetLastWin32Error(). Always wrap native calls in a NativeMethods class.
Imports System.Runtime.InteropServices
Public Class NativeMethods
' MessageBox from user32.dll
<DllImport("user32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
Public Shared Function MessageBox(hWnd As IntPtr, text As String, caption As String, type As UInteger) As Integer
End Function
' Beep from kernel32
<DllImport("kernel32.dll")>
Public Shared Function Beep(freq As UInteger, duration As UInteger) As Boolean
End Function
' GetSystemMetrics
<DllImport("user32.dll")>
Public Shared Function GetSystemMetrics(nIndex As Integer) As Integer
End Function
End Class
' usage
NativeMethods.MessageBox(IntPtr.Zero, "Hello from native!", "Test", 0)
NativeMethods.Beep(440, 500) ' 440 Hz for 500ms
' constants
Public Const SM_CXSCREEN As Integer = 0
Public Const SM_CYSCREEN As Integer = 1
Dim screenWidth = NativeMethods.GetSystemMetrics(SM_CXSCREEN)
' structures
<StructLayout(LayoutKind.Sequential)>
Public Structure POINT
Public X As Integer
Public Y As Integer
End Structure
<DllImport("user32.dll")>
Public Shared Function GetCursorPos(ByRef lpPoint As POINT) As Boolean
End Function
Dim p As POINT
GetCursorPos(p)
Console.WriteLine($"Cursor at ({p.X}, {p.Y})")COM interop
COM interop lets you call Office and other COM libraries. Add a reference to the interop assembly (or use NuGet). ALWAYS release COM objects with Marshal.ReleaseComObject — they don't get garbage collected promptly, leading to "ghost" Excel processes. Late binding (CreateObject) avoids compile-time references but loses IntelliSense and type safety.
Imports System.Runtime.InteropServices
Imports Excel = Microsoft.Office.Interop.Excel
' add reference: Microsoft.Office.Interop.Excel (or via NuGet)
Sub ExportToExcel(data As DataTable)
Dim app As Excel.Application = Nothing
Dim wb As Excel.Workbook = Nothing
Dim ws As Excel.Worksheet = Nothing
Try
app = New Excel.Application()
app.Visible = False
wb = app.Workbooks.Add()
ws = CType(wb.Worksheets(1), Excel.Worksheet)
' write headers
For c = 0 To data.Columns.Count - 1
ws.Cells(1, c + 1) = data.Columns(c).ColumnName
Next
' write data
For r = 0 To data.Rows.Count - 1
For c = 0 To data.Columns.Count - 1
ws.Cells(r + 2, c + 1) = data.Rows(r)(c).ToString()
Next
Next
ws.SaveAs("C: empexport.xlsx")
Finally
' ALWAYS release COM objects
If ws IsNot Nothing Then Marshal.ReleaseComObject(ws)
If wb IsNot Nothing Then
wb.Close(False)
Marshal.ReleaseComObject(wb)
End If
If app IsNot Nothing Then
app.Quit()
Marshal.ReleaseComObject(app)
End If
End Try
End Sub
' late binding (no reference needed)
Dim lateApp As Object = CreateObject("Excel.Application")
lateApp.Visible = True
lateApp.Quit()C# interop
VB and C# can call each other freely — they compile to the same IL. Add a project reference or shared library. Watch for case sensitivity (VB is case-insensitive), indexing syntax, and ref/out (VB uses ByRef). C# features like unsafe code, certain generics, and pattern matching have no VB equivalent — use C# for those parts.
' VB and C# interoperate seamlessly in the same project (multi-targeting)
' Add a C# project reference, or use a shared library
' C# code (in CSharpLib.dll):
' namespace Utils
' {
' public class Calculator
' {
' public int Add(int a, int b) => a + b;
' public static int Multiply(int a, int b) => a * b;
' }
' }
' VB usage:
Imports Utils
Dim calc As New Calculator()
Dim sum = calc.Add(2, 3) ' 5
Dim product = Calculator.Multiply(4, 5) ' 20
' differences to watch:
' - VB is case-insensitive; C# is case-sensitive
' - VB uses () for indexing; C# uses []
' - VB And/Or are bitwise (AndAlso/OrElse for short-circuit)
' - C# uses camelCase often; VB PascalCase by convention
' - C# has unsafe code and pointers; VB doesn't
' - C# has yield return; VB has Yield (similar)
' consuming async C# methods
Dim result = Await csharpObj.GetDataAsync()
' ref/out parameters
' C#: void TryParse(string s, out int result)
' VB: Integer.TryParse(s, result) ' result is ByRef
' using C# extension methods
Imports CSharpExtensions
Dim s = "hello".Capitalize() ' extension method from C# libCalling Python and other languages
For Python interop, Python.NET embeds CPython in .NET — direct object access. Simpler: shell out to python.exe with Process.Start. R.NET does the same for R. ClearScript embeds V8 for JavaScript. Choose based on integration depth: shell out for one-off scripts; embed for tight integration. Watch for performance (cross-runtime calls are slow).
' Python via Python.NET (NuGet: Python.Runtime)
' Install-Package Python.NET
' After setup:
' PythonEngine.Initialize()
' Using Py.GIL()
' Dim np As PyObject = Py.Import("numpy")
' Dim arr = np.array(New Integer() {1, 2, 3, 4, 5})
' Dim mean = np.mean(arr)
' Console.WriteLine(mean)
' End Using
' Or run Python script via process
Dim psi As New ProcessStartInfo With {
.FileName = "python.exe",
.Arguments = "script.py arg1 arg2",
.UseShellExecute = False,
.RedirectStandardOutput = True,
.CreateNoWindow = True
}
Using p As Process = Process.Start(psi)
Dim output = p.StandardOutput.ReadToEnd()
p.WaitForExit()
Console.WriteLine(output)
End Using
' R via R.NET (NuGet)
' Install-Package R.NET
' Dim engine = REngine.GetInstance()
' engine.Evaluate("x <- c(1, 2, 3, 4, 5)")
' Dim mean = engine.Evaluate("mean(x)").AsNumeric()[0]
' JavaScript via ClearScript (V8)
' Install-Package Microsoft.ClearScript.V8
' Dim engine As New V8ScriptEngine()
' engine.Execute("function add(a, b) { return a + b; }")
' Dim result = engine.Script.add(2, 3)
' command-line tools
Dim result = Process.Start("ffmpeg", "-i input.mp4 output.wav")Memory and pointers
Marshal.AllocHGlobal/FreeHGlobal allocate/free unmanaged memory — always pair them in Try/Finally. Marshal.Copy moves arrays between managed and unmanaged memory. VB doesn't support unsafe code (use C# for that, or use Marshal methods). Span(Of T) (modern .NET) gives pointer-like performance with memory safety — preferred over raw pointers.
Imports System.Runtime.InteropServices
' allocate native memory
Dim ptr As IntPtr = Marshal.AllocHGlobal(1024) ' 1KB
Try
' copy data
Marshal.WriteByte(ptr, 0, 42)
Marshal.WriteInt32(ptr, 4, 1234)
' copy array
Dim arr As Integer() = {1, 2, 3, 4}
Marshal.Copy(arr, 0, ptr, arr.Length)
' read back
Dim b = Marshal.ReadByte(ptr, 0)
Dim i = Marshal.ReadInt32(ptr, 4)
Dim arr2(3) As Integer
Marshal.Copy(ptr, arr2, 0, 4)
Finally
Marshal.FreeHGlobal(ptr)
End Try
' string marshaling
Dim strPtr As IntPtr = Marshal.StringToHGlobalUni("hello")
Try
' pass to native function
Finally
Marshal.FreeHGlobal(strPtr)
End Try
' pointer-sized fields
<StructLayout(LayoutKind.Sequential)>
Public Structure HandleInfo
Public Handle As IntPtr
Public Size As IntPtr
End Structure
' unsafe code (C# only — VB doesn't support unsafe)
' in C#: unsafe { int* p = &x; *p = 42; }
' in VB: use Marshal class or write a C# helper
' Span(Of T) — modern memory-safe pointer
Dim span As Span(Of Byte) = New Byte(1023) {}
span(0) = 42
Dim intSpan = MemoryMarshal.Cast(Of Byte, Integer)(span)Related Visual Basic snippets
Copy-paste ready code for common tasks.
Variables and Types
Declare variables with Dim and type inference.
Loops and Iteration
For, For Each, While, and Do loops in VB.
Sub and Function Procedures
Define Sub (no return) and Function (returns value).
Windows Forms Basics
Create a simple WinForms application.
File I/O
Read/write text files and use My.Computer.FileSystem.
Error Handling (Try/Catch)
Structured exception handling in VB.
Collections (List, Dictionary)
Generic collections in VB.NET.
LINQ Query Expressions
Query data with VB LINQ syntax.
Was this helpful?