Variáveis, Tipos e Operadores
Declaração de Variáveis e Tipos Embutidos
VB.NET é estaticamente tipado com declarações Dim. Tipos comuns: String, Integer (32-bit), Long (64-bit), Double (64-bit float), Decimal (alta precisão para financeiro), Boolean, Date, Char. Use o sufixo de tipo (D para Decimal, F para Single, L para Long) para forçar tipos literais. Option Explicit On força declaração de variáveis (evita erros de digitação). Option Infer On habilita inferência de tipo com Dim sem tipos explícitos. Tipos Nullable (Integer?) envolvem tipos de valor para que possam conter Nothing — útil para bancos de dados e 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 ModuleConstantes, Enums e Structures
Const declara constantes de tempo de compilação (implicitamente Shared, não podem mudar). Enum define constantes inteiras nomeadas — use [Enum].Parse para converter strings e CInt para obter o valor numérico. A notação de colchetes [Error] escapa palavras-chave reservadas. O atributo <Flags> marca um enum como um bit field, permitindo combinação com operadores Or/And — comum para permissões e opções. Sempre especifique o tipo subjacente (As Integer) para flags para controlar a largura de bits.
' 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 EnumConversão de Tipo e Casting
Conversões de ampliação (Integer para Double) são implícitas e seguras. Conversões de estreitamento (Double para Integer) exigem casting explícito. CInt/CStr/CDate/CDec são as funções de conversão do VB (CType é a versão genérica). DirectCast é o mais estrito — só funciona se o tipo em runtime corresponder exatamente. TryCast retorna Nothing em falha em vez de lançar (apenas tipos de referência). Sempre prefira TryParse a Parse para entrada do usuário para evitar exceções. IsNumeric/IsDate são auxiliares de validação úteis. CInt arredonda (banker's rounding) enquanto Int() faz floor.
' 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 IfOperadores e Expressões
VB usa \ para divisão inteira e / para divisão real (ao contrário de C# que usa / e %). Mod é o operador de resto. VB usa = tanto para atribuição quanto para comparação de igualdade (o contexto determina qual). And/Or avaliam ambos os operandos; AndAlso/OrElse fazem short-circuit (preferidos para desempenho e para evitar referências nulas). & é o operador de concatenação de strings (não +, que pode fazer adição numérica). Use AndAlso/OrElse por padrão para fazer short-circuit e evitar erros como verificar objetos Nothing.
' 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}" ' interpolationEntrada, Saída e Formatação
Console.WriteLine/Write são os métodos básicos de saída. Interpolação de strings ($"...") é a forma moderna e legível de incorporar expressões — suporta especificadores de formato como :F2 (2 decimais), :C (moeda), :P (porcentagem), :X (hex). String.Format usa placeholders posicionais {0}, {1} com alinhamento opcional ({0,-10} = alinhado à esquerda, largura 10). Para loops que constroem strings grandes, use StringBuilder (não &) para evitar criar muitas strings intermediárias. Integer.Parse lança erro em entrada inválida; use TryParse para segurança.
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 ModuleControle de Fluxo
If...Then...Else e Ternário
If...Then...ElseIf...Else é o condicional do VB. AndAlso/OrElse fazem short-circuit (não avaliam o lado direito se desnecessário) — sempre prefira esses a And/Or para condições. A função If() (VB 14+) é o operador ternário: If(condição, trueVal, falseVal). If() com dois argumentos é o operador de coalescência nula: If(maybeNull, defaultValue). Evite a antiga função IIf() — ela sempre avalia ambos os ramos e retorna Object (boxing). If de linha única não precisa de 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 é o switch do VB — mais limpo que If...ElseIf encadeados. Case suporta múltiplos valores (separados por vírgula), intervalos (To) e operadores de comparação (Is >= 90). Case Else é o ramo padrão. Ao contrário do switch do C#, o VB não faz fall through — cada Case executa independentemente e nenhum Break é necessário. Select Case funciona com strings, números e até objetos (usando Is). É ideal para despachar em valores discretos.
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 e Iterators
For...To...Next itera um intervalo com um Step opcional (negativo para contagem regressiva). For Each itera qualquer IEnumerable — mais limpo para coleções. Exit For quebra o loop; Continue For pula para a próxima iteração. Funções Iterator (com Yield) produzem sequências preguiçosamente — valores são gerados sob demanda, o que é eficiente em memória para sequências grandes ou infinitas. Yield retorna um valor, depois retoma de onde parou na próxima iteração. Este é o equivalente do VB ao yield return do C#.
' 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 e While
Do While executa enquanto a condição for True; Do Until executa enquanto a condição for False (até se tornar True). Colocar a condição no final (Loop While/Until) garante que o corpo execute pelo menos uma vez — útil para menus e validação de entrada. While...End While é a forma mais antiga e simples. Exit Do quebra; Continue Do pula para a próxima iteração. Escolha Do While quando você pode não entrar no loop; Do...Loop While quando você deve executar pelo menos uma vez.
' 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 e GoTo
With...End With é syntactic sugar para acessar múltiplos membros de um objeto sem repetir o nome da variável — útil para inicialização e builders. GoTo pula para um rótulo; o VB moderno desencoraja seu uso exceto para sair de loops profundamente aninhados ou no legado On Error GoTo. On Error GoTo é tratamento de erro estilo VB6 (Resume Next pula a linha com erro) — prefira Try/Catch estruturado em código novo. O objeto Err contém a descrição e o número do último erro.
' 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 NextProcedimentos, Funções e Eventos
Básico de Sub e Function
Sub executa uma ação (sem valor de retorno); Function retorna um valor. Return sai da função com um valor. O estilo antigo 'atribuir ao nome da função' (Multiply = a * b) ainda funciona, mas Return é mais claro. Ao chamar uma Sub, parênteses são opcionais (Greet "Alice" ou Greet("Alice")). Funções chamadas por seu valor de retorno exigem parênteses. Por padrão, o VB permite chamar funções sem atribuir o valor de retorno, mas isso pode ser confuso — seja explícito.
' 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)Parâmetros: ByVal, ByRef, Optional, ParamArray
ByVal (padrão) passa uma cópia — mudanças internas não afetam o chamador. ByRef passa uma referência — mudanças AFETAM o chamador (como ref em C#). Use ByRef quando um procedimento deve modificar a variável do chamador ou retornar múltiplos valores. Parâmetros Optional têm padrões e devem vir após os obrigatórios. ParamArray aceita um número variável de argumentos (como params em C#) e deve ser o último parâmetro. Argumentos nomeados (name:=value) melhoram a legibilidade e permitem pular parâmetros opcionais.
' 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)Expressões Lambda e Delegates
Lambdas são funções inline: Function(...) para as que retornam um valor, Sub(...) para as que não retornam. Func(Of T, TResult) é um tipo de delegate embutido para funções; Action(Of T) é para subs (sem retorno). AddressOf cria um delegate a partir de um método nomeado. Lambdas são essenciais para LINQ (Where, Select, OrderBy recebem argumentos de função). Lambdas multilinha usam Function...End Function (ou Sub...End Sub). Delegates são ponteiros de função type-safe — úteis para callbacks, eventos e padrões de estratégia.
' 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")Eventos e Manipuladores de Eventos
Eventos habilitam o padrão observer: uma classe lança eventos e assinantes os manipulam. Declare com Event, dispare com RaiseEvent, manipule com Handles (WithEvents, tempo de compilação) ou AddHandler (runtime, dinâmico). WithEvents + Handles é declarativo, mas a variável deve ser de nível módulo/classe. AddHandler/RemoveHandler permitem inscrever/cancelar inscrição em runtime — útil para wiring dinâmico. Manipuladores de evento devem corresponder à assinatura do evento. Eventos são a espinha dorsal do Windows Forms e WPF (cliques de botão, carregamento de formulário, 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, Recursão e Membros Shared
Overloading permite que múltiplos métodos compartilhem um nome com listas de parâmetros diferentes — o compilador escolhe a melhor correspondência. A palavra-chave Overloads é exigida ao sobrecarregar através de herança. Recursão é quando uma função chama a si mesma — precisa de um caso base para terminar. Cuidado com estouro de pilha em recursão profunda. Membros Shared (static do C#) pertencem ao tipo, não a instâncias — chame-os via ClassName.Member sem criar um objeto. Construtores Shared executam uma vez por tipo, ideais para inicializar dados estáticos.
' 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 e Processamento de Texto
Básico de Strings e Imutabilidade
Strings em VB.NET são imutáveis — todo método (ToUpper, Replace, etc.) retorna uma NOVA string; a original não muda. Use = para comparação de igualdade (específico do VB). IndexOf retorna o primeiro índice de uma substring (-1 se não encontrado). Split quebra uma string em um array; Join combina um array em uma string. Trim remove espaços em branco (ou caracteres especificados) de ambas as extremidades. Para comparação sem distinção de maiúsculas/minúsculas, use StringComparison.OrdinalIgnoreCase. Para manipulação pesada de strings em 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 e Desempenho
String é imutável — todo & ou += cria uma nova string, copiando todo o conteúdo anterior. Em loops, isso é O(n²). StringBuilder é mutável — Append/Insert/Remove modificam o mesmo buffer, tornando-o O(n). Sempre use StringBuilder para loops que constroem strings grandes. StringBuilder é encadeável (Append retorna a mesma instância). Defina a Capacity inicial se você souber o tamanho aproximado para evitar realocações. Para concatenações únicas (fora de loops), & é aceitável e mais legível.
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 capacityInterpolação de Strings e Formatação
Interpolação de strings ($"...") é a forma moderna de incorporar variáveis e expressões — legível e suporta especificadores de formato após dois pontos (:C moeda, :F2 decimais fixos, :N separadores de milhar, :X hex, :D5 preenchido com zeros, :P porcentagem). Formatos de data: yyyy-MM-dd, HH:mm:ss, dddd (nome completo do dia). Use String.Format para placeholders posicionais ou quando a string de formato é construída dinamicamente. Para apps conscientes de cultura (internacionalização), passe um CultureInfo para ToString/Format para controlar separadores decimais, símbolos de moeda e formatos de data.
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 €Expressões Regulares
Regex.IsMatch testa correspondência; Regex.Matches encontra todas; Regex.Match encontra a primeira. Grupos capturam partes da correspondência com parênteses — acesse via Groups(1), Groups(2). Regex.Replace substitui correspondências (use $1, $2 para referenciar grupos). RegexOptions.Compiled compila o padrão para correspondência repetida mais rápida. Padrões comuns: \d (dígito), \w (caractere de palavra), \s (espaço em branco), + (um ou mais), * (zero ou mais), {n} (exatamente n), ^/$ (início/fim). Sempre valide entrada do usuário com regex para emails, telefones, 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).CountOperações com Char e Encoding
Char é um único caractere Unicode (2 bytes). Use o sufixo c para literais char ("A"c). Métodos Char (IsDigit, IsLetter, IsUpper) são úteis para validação. Asc/Chr convertem entre chars e códigos ASCII. ToCharArray converte uma string em um array de char mutável (as strings em si são imutáveis). Encoding.UTF8.GetBytes converte strings em arrays de bytes (essencial para E/S de arquivos e rede). Base64 (Convert.ToBase64String) codifica dados binários como texto — usado em data URIs, anexos de email e 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 e Coleções
Arrays: Declaração, Indexação e Métodos
Arrays em VB são indexados em 0 e de tamanho fixo. Dim arr(n) cria n+1 elementos (0 a n). Literais de array usam { }. Arrays multidimensionais (,) são retangulares; jagged arrays ()() são arrays de arrays (cada linha pode ter comprimento diferente). ReDim redimensiona um array — Preserve mantém os valores existentes (sem ele, os valores são limpos). Array.Sort/Reverse/Clear/IndexOf/Copy são auxiliares estáticos. Use List(Of T) em vez de arrays quando o tamanho muda frequentemente.
' 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): Arrays Dinâmicos
List(Of T) é a coleção dinâmica padrão — cresce automaticamente conforme você Add. Count fornece o número de elementos (não a capacidade). Add/Insert/Remove/RemoveAt/Contains/IndexOf são os métodos principais. Find/FindAll/Exists recebem um predicado (lambda) para buscas personalizadas. Sort pode receber um delegate Comparison para ordenação personalizada. List(Of T) envolve um array internamente e o redimensiona quando cheio (Add amortizado O(1)). Converta para array com ToArray() quando precisar de uma coleção de tamanho fixo.
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 e Queue/Stack
Dictionary(Of TKey, TValue) mapeia chaves para valores — busca O(1) por chave. Use TryGetValue para evitar exceções e buscas duplas (ContainsKey + indexador). Itere com KeyValuePair. HashSet(Of T) armazena elementos únicos com add/contains O(1) — use para desduplicação e operações de conjunto (UnionWith, IntersectWith, ExceptWith). Queue é FIFO (Enqueue/Dequeue); Stack é LIFO (Push/Pop). Todas essas coleções são genéricas (type-safe, sem boxing). Escolha com base no padrão de acesso: lookup → Dictionary, unicidade → HashSet, ordem → 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: Sintaxe de Query e Method
LINQ (Language Integrated Query) transforma coleções declarativamente. Query syntax (From...Where...Select) é estilo SQL e legível para queries complexas. Method syntax (.Where().Select()) é fluente e encadeia bem. Ambas compilam para o mesmo código. Operadores principais: Where (filtrar), Select (transformar), OrderBy (ordenar), GroupBy (agrupar), Take/Skip (paginar), First/FirstOrDefault (encontrar), Any/All (testar), Sum/Average/Max/Min (agregar). FirstOrDefault retorna o padrão (0, Nothing) se não houver correspondência — evita exceções. LINQ funciona em qualquer IEnumerable (arrays, listas, dicionários).
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-likeInicialização de Coleção e Tuples
Inicializadores de coleção (From { }) criam e populam coleções em uma instrução. Tuples (VB 2017+) agrupam múltiplos valores — campos nomeados (X, Y) são mais claros que Item1, Item2. Tuples são perfeitos para retornar múltiplos valores de uma função sem definir uma classe. Desconstrução (Dim (a, b) = tuple) divide um tuple em variáveis. ToDictionary converte uma lista em um dicionário (seletor de chave + seletor de valor). Tuples são tipos de valor (structs), então são eficientes para pequenos agrupamentos temporários.
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")) ' 30Programação Orientada a Objetos
Classe, Fields e Properties
Classes agrupam dados (fields/properties) e comportamento (methods). Auto-implemented properties (Property X As Type) geram automaticamente um backing field oculto — conciso para dados simples. Full properties (blocos Get/Set) permitem validação, computação ou efeitos colaterais. Properties ReadOnly têm apenas um Get. Me refere-se à instância atual (como this em C#). Construtores (Sub New) inicializam objetos. Properties parecem fields para chamadores (p.Name), mas executam código — esse encapsulamento é o benefício central da POO.
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)Herança e MyBase
Inherits estabelece uma relação IS-A (Dog IS an Animal). MyBase.New() chama o construtor base (deve ser a primeira instrução). Overridable (base) + Overrides (derivada) habilitam polimorfismo — o método derivado executa mesmo quando acessado via referência base. Shadows (ou Overloads) oculta um membro base em vez de sobrescrever (sem polimorfismo). Use Overridable/Overrides para polimorfismo verdadeiro; Shadows apenas quando você deve ocultar um membro que não pode alterar. VB suporta herança única (uma classe base), mas múltiplas 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 e Polimorfismo
Interfaces definem um contrato (métodos, properties) sem implementação — classes as Implement. Ao contrário da herança, uma classe pode implementar múltiplas interfaces. A palavra-chave Implements vincula um membro à sua declaração de interface (sintaxe específica do VB). Interfaces habilitam polimorfismo: código pode trabalhar com qualquer IDrawable sem saber se é Circle ou Square. TypeOf x Is T verifica o tipo em runtime; DirectCast faz cast (lança erro se inválido). Use interfaces para desacoplar código: dependa de IDrawable, não de Circle. Este é o princípio da Inversão de Dependência.
' 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 e Constraints
Generics (Of T) permitem escrever código type-safe e reutilizável que funciona com qualquer tipo — sem boxing, sem casts, verificação de tipo em tempo de compilação. List(Of T), Dictionary(Of K,V), Stack(Of T) são coleções genéricas. Constraints restringem o parâmetro de tipo: Class (tipo de referência), Structure (tipo de valor), New (tem construtor sem parâmetros — permite New T()), classe base ou interface específica. Generics evitam o impacto de desempenho do boxing (tipos de valor) e a fragilidade de casts de Object. Sempre prefira coleções genéricas (List(Of T)) a não genéricas (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 e Namespaces
Partial classes dividem uma classe em múltiplos arquivos — o compilador os mescla. Útil para separar código gerado (arquivos designer) de código escrito à mão, ou dividir classes grandes. Namespaces organizam tipos e previnem colisões de nomes (MyApp.Models.User vs MyApp.Services.User). Imports traz nomes de namespace para o escopo (sem necessidade de qualificação completa). O RootNamespace (configuração do projeto) envolve todos os tipos — defina-o com o nome da sua empresa/produto. Convenções: namespaces correspondem à estrutura de pastas; um namespace por área lógica.
' 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 ModuleTratamento de Erros e Exceções
Try...Catch...Finally
Try/Catch/Finally é tratamento de exceção estruturado. Blocos Catch são verificados em ordem — coloque exceções específicas primeiro, Exception (base) por último. Finally sempre executa (mesmo em Return ou exceções não capturadas) — use-o para limpeza (fechar arquivos, liberar recursos). Throw (puro) re-lança a exceção atual, preservando o stack trace. When filtra condicionalmente (Catch...When condição). Propriedades de Exception: Message (descrição), StackTrace (cadeia de chamada), Source (assembly). Nunca capture Exception silenciosamente sem logar — isso oculta 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 TryLançando e Exceções Personalizadas
Throw New ExceptionType(...) lança uma exceção. Sempre passe uma mensagem significativa e use NameOf() para nomes de parâmetros (seguro para refatoração). Ao envolver exceções, passe a original como innerException para preservar a causa. Exceções personalizadas devem herdar de Exception (ou uma base mais específica), ser marcadas <Serializable> e fornecer construtores (message, message+inner). Sobrescreva ToString() para exibição personalizada. Exceções personalizadas permitem que chamadores capturem tipos específicos de erro e os tratem adequadamente — use-as para erros de domínio (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 TryInstrução Using e IDisposable
Using garante que Dispose() seja chamado quando o bloco sai — crítico para recursos (arquivos, conexões de banco de dados, streams de rede) que não são coletados pelo garbage collector prontamente. Múltiplos recursos podem ser declarados em um Using (separados por vírgula). Implemente IDisposable quando sua classe contém recursos não gerenciados ou outros objetos IDisposable. O padrão Dispose: Dispose(disposing As Boolean) libera recursos gerenciados (quando disposing=True) e não gerenciados; GC.SuppressFinalize impede o finalizer de executar. Sempre envolva objetos IDisposable em Using para prevenir vazamentos de recursos.
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 UsingMelhores Práticas de Exceção
Melhores práticas de exceção: (1) Não capture exceções que você não pode realmente tratar — deixe-as propagar. (2) Nunca engula exceções silenciosamente (Catch vazio) — no mínimo logue-as. (3) Use TryParse em vez de Parse para entrada do usuário (exceções são caras e para casos verdadeiramente excepcionais). (4) Lance tipos de exceção específicos (ArgumentOutOfRangeException, não Exception). (5) Use filtros When para logar sem capturar (retorne False para não capturar). (6) Re-lance com Throw (puro) para preservar o stack trace — não Throw ex que o reseta. Exceções são para condições excepcionais, não fluxo de controle normal.
' 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 FunctionDepuração e Diagnóstico
Debug.Assert/WriteLine só executam em builds Debug (compilados fora em Release) — use para invariantes e diagnósticos. Trace funciona em ambos os builds — para log de produção. #If DEBUG...#End If habilita compilação condicional. Debugger.Break() atua como um breakpoint programático. Stopwatch mede tempo decorrido precisamente (para benchmarking). StackTrace captura a cadeia de chamada (útil para log). EventLog escreve no Windows Event Log (exige admin para criar a origem). Use essas ferramentas para diagnosticar problemas sem modificar o comportamento de produção.
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}")E/S de Arquivos e Streams
Leitura e Escrita de Arquivo de Texto
File.WriteAllText/ReadAllText são os mais simples para arquivos pequenos. File.ReadAllLines retorna um array de String (carrega o arquivo inteiro na memória). Para arquivos grandes, use StreamReader/StreamWriter com Using — eles leem/escrevem linha por linha, mantendo a memória baixa. Using garante que o stream seja fechado mesmo em exceções. AppendAllText/AppendAllLines adicionam a arquivos existentes. Sempre verifique File.Exists antes de ler para dar um erro amigável. Para E/S async (UI não bloqueante), use ReadAllTextAsync/WriteAllTextAsync com 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 FunctionArquivos Binários e Serialização
BinaryWriter/Reader leem/escrevem tipos primitivos em um formato binário compacto — a ordem de leitura deve corresponder à de escrita. Para dados estruturados, prefira JSON (legível por humanos, independente de linguagem). System.Text.Json (embutido, rápido) ou Newtonsoft.Json (popular, rico em recursos) serializam objetos de/para JSON. JsonSerializer.Serialize/Deserialize(Of T) são os métodos principais. Use JsonSerializerOptions para formatação (indentado, camelCase). JSON é ideal para arquivos de configuração, APIs e troca de dados. Evite BinaryFormatter (risco de segurança — removido no .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)Operações de Diretório e Path
Directory.CreateDirectory/GetFiles/GetDirectories/Exists/Delete gerenciam pastas. GetFiles suporta padrões de busca (*.csv) e SearchOption.AllDirectories para recursão. A classe Path manipula caminhos com segurança entre plataformas — sempre use Path.Combine (não &) para juntar caminhos (lida com separadores). Path.GetTempFileName cria um arquivo temporário único. FileInfo/DirectoryInfo são wrappers orientados a objetos sobre operações de arquivo/diretório com propriedades (Length, CreationTime) e métodos (CopyTo, MoveTo, Delete). Use-os em vez de manipulação de strings para caminhos.
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)
NextParsing de CSV e Arquivos de Dados
Para CSV simples, Split(","c) funciona — mas quebra em campos citados com vírgulas ("Smith, John"). TextFieldParser (em Microsoft.VisualBasic.FileIO) lida com aspas, aspas escapadas e delimitadores corretamente — use-o para CSV real. Ele também analisa arquivos de largura fixa (FieldWidths). Para escrever CSV, cite manualmente campos contendo vírgulas/aspas. My.Computer.FileSystem é um wrapper de conveniência específico do VB (API mais simples, menos controle). Para CSVs grandes, considere CsvHelper (NuGet) — uma biblioteca CSV robusta e streaming. Sempre trate a linha de cabeçalho separadamente.
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})Operações de Arquivo Async
E/S de arquivo async usa ReadToEndAsync/ReadLineAsync/WriteAsync com Await — a thread não é bloqueada durante a E/S, melhorando a responsividade (especialmente em apps de UI). Métodos async retornam Task ou Task(Of T); Await desembrulha o resultado. Para processar muitos arquivos concorrentemente, inicie todas as tarefas e Await Task.WhenAll (E/S paralela). Async Sub é para manipuladores de eventos; Async Function para todo o resto. E/S async brilha em servidores web (lidando com muitas requisições) e apps desktop (mantendo a UI responsiva). O overhead é pequeno, então use async para qualquer E/S que possa ser lenta.
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 e UI
Básico de Form e Eventos
Windows Forms (WinForms) constrói UIs desktop adicionando Controls a um Form. Cada controle tem propriedades (Text, Location, Size) e eventos (Click, Load, FormClosing). AddHandler conecta eventos em runtime; Handles (com WithEvents) conecta em tempo de compilação. MessageBox.Show exibe diálogos. OnLoad/OnFormClosing são métodos protegidos sobrescritos para o ciclo de vida do formulário. Application.Run inicia o loop de mensagens. WinForms usa o designer (arrastar e soltar no Visual Studio) — o código gerado vai em uma partial class .Designer.vb. Para apps modernos, considere WPF ou 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 ModuleControles Comuns
Controles comuns do WinForms: TextBox (entrada de texto, Multiline para área), CheckBox (booleano), RadioButton (mutuamente exclusivos — agrupe em GroupBox), ComboBox (dropdown), NumericUpDown (entrada numérica com spinner), ListBox (lista selecionável). Defina Location (Point) e Size. Use inicializadores de objeto (With {.Prop = value}) para configuração concisa. Controls.AddRange adiciona múltiplos controles de uma vez. Cada controle tem um evento padrão (Button.Click, TextBox.TextChanged, ComboBox.SelectedIndexChanged) — dê um duplo clique no designer para gerar um manipulador.
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 e Anchoring
Layout do WinForms: Dock (Top/Bottom/Left/Right/Fill) faz um controle preencher uma borda — adicione Fill por último para que ocupe o espaço restante. Anchor fixa um controle às bordas do parent (ele redimensiona quando o formulário redimensiona). TableLayoutPanel organiza controles em uma grade (linhas/colunas com tamanhos percentuais/absolutos) — melhor para formulários. FlowLayoutPanel empilha controles em um fluxo (quebra automaticamente). Panel é um contêiner simples para agrupamento. Para layouts responsivos, prefira TableLayoutPanel ao posicionamento manual. A ordem de Controls.Add importa para docking (adições posteriores sobrepõem as anteriores).
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 ClassDiálogos: OpenFileDialog, SaveFileDialog, ColorDialog
Diálogos comuns: OpenFileDialog (selecionar arquivo para abrir), SaveFileDialog (escolher onde salvar), ColorDialog (escolher uma cor), FontDialog (escolher uma fonte), FolderBrowserDialog (selecionar um diretório). Filter usa o formato "Descrição|padrão|Descrição|padrão". ShowDialog retorna DialogResult (OK/Cancel) — sempre verifique antes de usar o resultado. Envolva diálogos em Using (eles são IDisposable). OverwritePrompt previne sobrescritas acidentais. InitialDirectory inicia o diálogo em um local útil. Esses diálogos fornecem UI nativa do Windows sem código personalizado.
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 e DataGridView
Data binding conecta controles de UI a objetos de dados automaticamente. Binding simples: txt.DataBindings.Add("Text", obj, "PropName"). Para listas, defina DataGridView.DataSource para uma BindingList(Of T) ou DataTable. INotifyPropertyChanged habilita binding bidirecional — a UI atualiza quando a propriedade muda (lance PropertyChanged no setter). BindingList(Of T) é como ObservableCollection — notifica a grade quando itens são adicionados/removidos. BindingSource adiciona navegação e filtragem. AutoGenerateColumns cria colunas a partir de propriedades. Data binding elimina código manual de sincronização entre UI e dados.
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 ClassAprofundamento em E/S de Arquivos
StreamReader e StreamWriter
StreamReader/StreamWriter lidam com arquivos de texto com encoding e disposição adequados. O bloco Using garante que os arquivos sejam fechados mesmo em exceções — sempre o use. File.ReadAllLines/ReadAllText são convenientes para arquivos pequenos; ReadLine em um loop é eficiente em memória para arquivos grandes. Métodos async (ReadAllTextAsync) previnem congelamento da UI durante E/S. StreamWriter com append:=True adiciona a arquivos existentes. O encoding padrão é UTF-8; especifique explicitamente com New StreamWriter(path, append, Encoding.UTF8) para formatos legados.
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")Manipulação de Arquivos Binários e CSV
BinaryWriter/Reader armazenam tipos primitivos em um formato binário compacto — muito menor que texto e mais rápido de analisar. Sempre leia na mesma ordem e tipos que escreveu. Para CSV, TextFieldParser lida com campos citados com vírgulas incorporadas corretamente (ao contrário de Split ingênuo). Para CSV de produção, use uma biblioteca como CsvHelper (NuGet) que lida com casos extremos, mapeamento de tipos e streaming. Arquivos binários são específicos de plataforma (endianness); use BinaryWriter com little-endian para compatibilidade entre plataformas.
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 UsingOperações de Sistema de Arquivos
File e FileInfo fornecem operações de arquivo; Directory e DirectoryInfo lidam com pastas. FileInfo/DirectoryInfo são mais eficientes quando você precisa de múltiplos atributos (uma chamada de sistema). EnumerateFiles é preguiçoso (produz um de cada vez) vs GetFiles que carrega todos os caminhos na memória — use Enumerate para diretórios grandes. Path.Combine constrói caminhos com separadores corretos entre plataformas; nunca concatene strings manualmente. SearchOption.AllDirectories pode lançar erro em acesso negado; capture UnauthorizedAccessException ou use um auxiliar recursivo.
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 pathSerialização (JSON e XML)
System.Text.Json (moderno, rápido) é preferido ao legado DataContractJsonSerializer. JsonSerializer.Serialize/Deserialize lidam com a maioria dos tipos; use JsonSerializerOptions para formatação e políticas de nomenclatura. Para XML, XmlSerializer é simples, mas limitado (sem dicionários, exige construtor sem parâmetros). Para cenários complexos (polimorfismo, referências circulares), considere Newtonsoft.Json (Json.NET) via NuGet — mais recursos, mas mais lento. Sempre trate erros de deserialização (JsonException) para entrada não confiável.
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 UsingOperações de Arquivo Async e Streams
E/S de arquivo async mantém UIs responsivas durante operações grandes. ReadAsync/WriteAsync funcionam com buffers para relatórios de progresso refinados. IProgress(Of T) relata progresso de volta à thread da UI com segurança. MemoryStream é para dados em memória (sem disco). GZipStream comprime/descomprime streams em tempo real. Para E/S de rede, use HttpClient (async). Sempre use blocos Using para garantir que os streams sejam fechados. Tamanho de buffer 81920 (80KB) é um bom padrão — equilibra memória e overhead de syscall.
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 UsingAcesso a Banco de Dados ADO.NET
Conexões e Comandos
Sempre use queries parametrizadas (Parameters.AddWithValue) para prevenir injeção de SQL — nunca concatene entrada do usuário em strings SQL. SqlConnection, SqlCommand e SqlDataReader são todos IDisposable; envolva-os em blocos Using. ExecuteReader retorna linhas; ExecuteNonQuery retorna linhas afetadas (INSERT/UPDATE/DELETE); ExecuteScalar retorna um único valor (COUNT, SUM). Para outros bancos de dados, use o provider apropriado (OracleConnection, OleDbConnection, OdbcConnection) ou o DbProviderFactory genérico.
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 e DataTables
DataTable/DataSet são contêineres de dados desconectados — carregue uma vez, trabalhe offline, atualize depois. SqlDataAdapter.Fill carrega dados; SqlCommandBuilder gera automaticamente comandos INSERT/UPDATE/DELETE para cenários simples de tabela única. DataSets tipados (via designer .xsd) fornecem verificação de tipo em tempo de compilação e IntelliSense. DataView filtra e ordena sem modificar o DataTable subjacente. Para aplicações modernas, prefira Entity Framework ou Dapper a DataSets brutos — eles são mais sustentáveis e testáveis. DataSets permanecem úteis para relatórios e interop legado.
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"))
NextTransações e Connection Pooling
Transações garantem atomicidade — todas as operações têm sucesso ou todas falham. SqlTransaction envolve múltiplos comandos em uma conexão. TransactionScope habilita transações distribuídas entre múltiplas conexões/recursos (usa MS DTC se necessário) — chame scope.Complete() para commit. Connection pooling é automático no ADO.NET: conexões são reutilizadas em vez de recriadas, melhorando drasticamente o desempenho. Sempre feche/descarte conexões (blocos Using) para devolvê-las ao pool. Configure tamanho e tempo de vida do pool na string de conexão para apps de alto desempenho.
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 poolOperações de Banco de Dados Async
Operações de banco de dados async (OpenAsync, ExecuteReaderAsync, ReadAsync) previnem congelamento da UI durante queries longas. O padrão espelha o ADO.NET síncrono, mas com Await. Sempre use Async em aplicações de UI para mantê-las responsivas. Para servidores de alto desempenho, chamadas DB async liberam threads para lidar com outras requisições. MARS (Multiple Active Result Sets) permite múltiplos leitores em uma conexão — habilite com 'MultipleActiveResultSets=True' na string de conexão. Trate SqlException para erros específicos de banco de dados (deadlocks, violações de constraints).
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 é um micro-ORM que estende IDbConnection com métodos de extensão — rápido (próximo à velocidade do ADO.NET bruto) e simples. Query<T> mapeia linhas para objetos automaticamente correspondendo nomes de colunas a propriedades. Objetos anônimos fornecem parâmetros (seguros contra injeção de SQL). Bulk insert passa uma lista e Dapper executa uma vez por item. Multi-mapping lida com joins dividindo linhas em uma coluna. Dapper é ideal quando você quer controle de SQL com menos boilerplate que ADO.NET bruto. Para grafos de objetos complexos e rastreamento de mudanças, use Entity Framework.
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
Sintaxe de Query e Method
LINQ fornece duas sintaxes: query (estilo SQL, From...Where...Select) e method (fluente, .Where().Select()). Elas compilam para o mesmo IL — escolha com base na legibilidade. A sintaxe de query suporta menos operadores (sem Sum, Count diretamente); use a sintaxe de método para esses. LINQ usa execução deferida — a query não é executada até você iterar (For Each) ou chamar um operador de materialização (ToList, Count). Isso significa que modificar a origem após definir a query afeta os resultados. Use .ToList() para forçar execução imediata e capturar os dados.
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
NextAgregação e Agrupamento
Operadores de agregação do LINQ (Sum, Average, Min, Max, Count, Any, All) colapsam sequências para valores únicos. GroupBy particiona elementos por uma chave; cada grupo tem um .Key e é ele mesmo enumerável. A sintaxe de query 'Group By...Into Group' é específica do VB. Múltiplas agregações por grupo são comuns para relatórios. Any/All fazem short-circuit (param cedo) — útil para verificar condições sem iteração completa. Funções de agregação lançam erro em sequências vazias; use as variantes nullable (Sum retorna 0, Average lança) ou DefaultIfEmpty para lidar com isso.
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 e Operações de Conjunto
Join realiza inner joins (chaves correspondentes); Group Join com DefaultIfEmpty cria left joins. Zip pareia elementos posição a posição. Operações de conjunto (Union, Intersect, Except, Distinct) usam comparadores de igualdade padrão — sobrescreva Equals/GetHashCode ou passe um IEqualityComparer personalizado para tipos complexos. Concat anexa sem remover duplicatas (ao contrário de Union). Todas essas são preguiçosas exceto quando materializadas. Para grandes conjuntos de dados, considere usar um HashSet ou Dictionary para buscas O(1) em vez de loops aninhados O(n*m) do Join.
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,6Ordenação, Paginação e Operações de Elemento
OrderBy/ThenBy ordenam (estável); OrderByDescending/ThenByDescending ordenam descendente. Múltiplas chaves de ordenação usam ThenBy após OrderBy. Paginação usa Skip (offset) e Take (limit) — essencial para grandes conjuntos de dados em UIs. First/Last lançam erro em vazio; FirstOrDefault retorna o valor padrão (Nothing para tipos de referência, 0 para números). Single impõe exatamente uma correspondência (lança caso contrário) — útil para validação. TakeWhile/SkipWhile particionam com base em um predicado até ele falhar. Todas essas são preguiçosas exceto First/Last/Single que forçam iteração.
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)LINQ Personalizado e Árvores de Expressão
Métodos de extensão (decorados com <Extension()>) adicionam operadores estilo LINQ a qualquer tipo. Agregações personalizadas (Median, Mode, StandardDeviation) preenchem lacunas nos operadores embutidos. IEnumerable(Of T) usa delegates (em memória); IQueryable(Of T) usa árvores de expressão que traduzem para SQL (EF/LINQ to SQL) — misturá-los pode causar avaliação no cliente (lenta). ToList/ToArray/ToDictionary forçam avaliação e capturam resultados. PLINQ (.AsParallel) paraleliza queries — use para operações CPU-bound em grandes coleções, mas cuidado com ordenação e 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 e Async
Tasks e Async/Await
Async/Await é a forma moderna de escrever código assíncrono — parece síncrono, mas não bloqueia threads. Marque métodos Async e use Await em chamadas que retornam Task. Manipuladores de evento de UI podem ser Async Sub (o único lugar onde Async Sub é aceitável). Task.Run descarrega trabalho CPU-bound para o thread pool. Task.WhenAll espera por todas as tarefas (paralelo); Task.WhenAny espera pela primeira a completar. O compilador gera uma máquina de estados que lida com continuação, propagação de exceção e captura de contexto. Sempre prefira Async/Await a threading manual ou 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 e Sincronização
SyncLock (VB) / lock (C#) fornece exclusão mútua — apenas uma thread entra no bloco por vez. Bloqueie em um Object privado, nunca em Me ou uma string. Interlocked fornece operações atômicas sem locks (mais rápido para casos simples). Mutex funciona entre processos (apps de instância única). SemaphoreSlim limita acesso concorrente (ex.: máximo 3 chamadas de API por vez). Sempre libere locks/semáforos em blocos Finally. Deadlocks ocorrem quando duas threads esperam uma pela outra — adquira locks em ordem consistente. Use ConcurrentDictionary, ConcurrentQueue para coleções thread-safe sem locks.
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 e BackgroundWorker
Parallel.For/ForEach particionam trabalho entre threads do thread pool — ideal para loops CPU-bound. PLINQ (.AsParallel) paraleliza queries LINQ. Use ParallelOptions.MaxDegreeOfParallelism para limitar threads. CancellationToken habilita cancelamento cooperativo. BackgroundWorker é legado, mas conveniente para WinForms — ele marshala ProgressChanged e RunWorkerCompleted para a thread da UI automaticamente. Para código novo, prefira Task.Run + IProgress(Of T) + Async/Await. Loops Parallel são bloqueantes (ao contrário de Async); não os use em threads de UI.
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 e Marshaling de Thread de UI
Existem três Timers no .NET: Windows.Forms.Timer (thread de UI, mais simples para formulários), System.Threading.Timer (thread pool, leve) e System.Timers.Timer (componente, cenários de servidor). Apenas Forms.Timer pode atualizar a UI diretamente; os outros exigem Invoke. Control.Invoke marshala um delegate para a thread de UI — verifique InvokeRequired primeiro. ConfigureAwait(False) melhora o desempenho em código de biblioteca (sem captura de contexto), mas impede acesso à UI depois. SynchronizationContext.Post é uma forma mais geral de marshalar para um contexto específico (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)Coleções Concorrentes e Channels
Coleções concorrentes (ConcurrentDictionary, Queue, Stack, Bag) são alternativas thread-safe a coleções regulares — use-as em vez de bloquear. AddOrUpdate/GetOrAdd no ConcurrentDictionary são operações compostas atômicas. BlockingCollection implementa o padrão producer-consumer com Add/Take bloqueantes — ótimo para filas de worker. Channels (System.Threading.Channels) são a alternativa moderna e async-friendly com backpressure. Para thread safety de UI, use Invoke/BeginInvoke em vez de coleções concorrentes. Sempre prefira essas primitivas embutidas a bloqueio manual — elas são testadas e otimizadas.
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 e API do Windows
Leitura e Escrita no Registry
O Windows Registry armazena configurações de aplicação e configuração do sistema. Use Microsoft.Win32.Registry para acesso. HKEY_CURRENT_USER (HKCU) é por usuário (sem admin necessário); HKEY_LOCAL_MACHINE (HKLM) é de todo o sistema (exige admin). Sempre forneça um valor padrão ao ler (retorna Nothing se ausente). Use RegistryValueKind para especificar o tipo (String, DWord, Binary). A chave Run inicia apps automaticamente no login. Envolva RegistryKey em Using para fechar handles. Tenha cuidado ao editar o registry — erros podem quebrar o 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: Chamando a API do Windows
P/Invoke (Platform Invoke) permite que o VB chame funções da API do Windows não gerenciada via DllImport. Declare a assinatura da função correspondente à API C; o runtime marshala tipos automaticamente (String ↔ LPSTR, Integer ↔ DWORD). Use ByRef para parâmetros de saída e StructLayout para estruturas passadas por referência. CharSet.Auto seleciona ANSI ou Unicode com base no SO. Bibliotecas comuns: user32 (janelas, mensagens), kernel32 (sistema, arquivos), gdi32 (gráficos). Sempre envolva P/Invoke em uma classe NativeMethods. Pinvoke.net é um ótimo recurso para assinaturas.
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_CYSCREENOperações de Process e Shell
Process.Start lança programas externos. UseShellExecute=False com RedirectStandardOutput permite capturar saída programaticamente — essencial para ferramentas de linha de comando. Sempre WaitForExit e leia a saída para evitar deadlocks (o buffer de saída pode encher e bloquear o processo). GetProcesses enumera processos em execução; GetProcessesByName encontra específicos. Verb='runas' eleva para admin (dispara UAC). Para processos de longa duração, inscreva-se no evento Exited ou use padrões async. Tenha cuidado com caminhos de arquivo fornecidos pelo usuário — valide para prevenir injeção de comando.
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)Ambiente e Informações do Sistema
Environment fornece informações do sistema e do usuário. GetFolderPath com SpecialFolder é a forma correta de encontrar diretórios padrão (AppData, MyDocuments, Temp) — nunca hardcode caminhos. Variáveis de ambiente configuram comportamento entre máquinas. GetCommandLineArgs inclui o executável como primeiro elemento. Para informações detalhadas de hardware, use WMI (System.Management) — consulte classes como Win32_Processor, Win32_LogicalDisk. Screen.AllScreens (Windows Forms) fornece informações de monitor para configurações multi-display. Sempre use essas APIs em vez de adivinhar caminhos ou configurações.
' 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}")
NextArquivos INI e Configuração
Arquivos INI são legados, mas ainda usados para configurações simples. As funções da API do Windows (WritePrivateProfileString, GetPrivateProfileString) lidam com análise. Para .NET moderno, use app.config (XML) ou appsettings.json (JSON) com ConfigurationManager ou Microsoft.Extensions.Configuration. Config JSON suporta estruturas aninhadas, arrays e substituições específicas de ambiente (appsettings.Production.json). Configuration builders podem combinar múltiplas fontes (arquivos, variáveis de ambiente, linha de comando). Sempre separe configuração de código para flexibilidade de implantação.
' 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 }Coleções e Generics Aprofundado
List, Dictionary e HashSet
List(Of T) é o cavalo de batalho — array dinâmico com append O(1), insert/remove O(n). Dictionary(Of K,V) fornece lookup por chave O(1) (hash table). HashSet(Of T) armazena elementos únicos com operações de conjunto rápidas (Union, Intersect, Except). SortedDictionary mantém chaves ordenadas (árvore de busca binária). LinkedList é duplamente encadeada (insert/remove rápido em qualquer lugar, mas sem acesso indexado). Escolha com base nos padrões de acesso: List para indexado, Dictionary para chaveado, HashSet para pertinência, SortedDictionary para iteração ordenada. Todos são genéricos (type-safe, sem 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 e ObservableCollection
Queue (FIFO) e Stack (LIFO) são especializados para padrões de acesso ordenado. ObservableCollection lança eventos CollectionChanged quando itens são adicionados/removidos/substituídos — essencial para data binding MVVM. BindingList estende isso com capacidades de edição (AddingNew, AllowEdit, AllowRemove). ReadOnlyCollection envolve uma lista para prevenir modificação (retorna a original, não uma cópia). KeyedCollection combina semânticas de lista e dicionário (acesso por índice ou chave). Para versões thread-safe, use ConcurrentQueue, ConcurrentStack, ConcurrentDictionary de 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 ClassMétodos Genéricos e Constraints
Generics fornecem segurança de tipo sem overhead de boxing/unboxing. Constraints (Of T As ...) restringem parâmetros de tipo: Class (tipos de referência), Structure (tipos de valor), New (construtor sem parâmetros), IComparable (interface) ou uma classe base. Múltiplas constraints usam chaves: Of T As {Class, New, IComparable(Of T)}. Métodos genéricos inferem o tipo a partir de argumentos. Generics são reificados no .NET (informação de tipo disponível em runtime, ao contrário da type erasure do Java). Use generics para coleções, algoritmos e classes utilitárias para evitar duplicação de código mantendo a segurança de tipo.
' 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 ClassColeções Personalizadas e IEnumerable
Implementar IEnumerable(Of T) habilita iteração For Each. O padrão Iterator/Yield (VB 14+) gera máquinas de estados para avaliação preguiçosa — valores são produzidos sob demanda. Isso é eficiente em memória para sequências grandes ou infinitas. CollectionBase é uma classe base legada; prefira implementar ICollection(Of T) ou herdar de List(Of T) para código novo. Coleções personalizadas fazem sentido quando você precisa de comportamento especial (validação, notificações, lazy loading). Para a maioria dos casos, componha coleções existentes em vez de construir do zero.
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 e ValueTuple
ValueTuple (VB 15+) é a forma moderna de retornar múltiplos valores — leve (tipo de valor), com campos nomeados para legibilidade. Tuple (System.Tuple) é um tipo de referência apenas com nomes Item1/Item2. ValueTuple suporta desconstrução (Dim (a, b) = ...) e igualdade elemento a elemento. Use ValueTuple para retornos internos de métodos e resultados intermediários de LINQ. Para APIs públicas, prefira records ou classes com nomes significativos. O padrão discard (_) ignora valores indesejados. ValueTuple é especialmente útil em LINQ para projeções anônimas que precisam ser retornadas de métodos.
' 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 middleDepuração e Diagnóstico
Debug e Trace
A classe Debug é compilada fora em builds Release (usa ConditionalAttribute); Trace funciona em todos os builds. TraceListeners direcionam a saída para arquivos, console ou event log. TraceSwitch (configurável via app.config) controla a verbosidade em runtime sem recompilar. TraceSource fornece tracing nomeado e granular com tipos de evento (Error, Warning, Info, Verbose). Sempre use Trace para diagnósticos de produção e Debug para asserções de desenvolvimento. O atributo Conditional remove chamadas de método inteiramente em builds Release — overhead zero. Configure listeners em app.config para flexibilidade de implantação.
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 e Breakpoints
Atributos de debugger personalizam como objetos aparecem no depurador. DebuggerDisplay controla a string de resumo; DebuggerBrowsable controla a visibilidade de membros (RootHidden achata coleções). DebuggerStepThrough/Hidden controlam o comportamento de stepping. Breakpoints condicionais pausam apenas quando uma condição é verdadeira — essencial para encontrar bugs em loops grandes. Tracepoints logam mensagens sem modificar código. A Immediate Window avalia expressões em runtime. Edit and Continue permite corrigir código sem reiniciar. Domine esses recursos do debugger para acelerar drasticamente a depuração.
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 = newValueEstratégias de Tratamento de Exceções
Capture exceções específicas antes das genéricas (mais específicas primeiro). Use Throw (não Throw ex) para preservar o stack trace original. Filtros de exceção (cláusula When) adicionam condições sem capturar — a exceção propaga se o filtro for falso. Exceções personalizadas devem herdar de Exception (não ApplicationException, que está obsoleto), ser Serializable e implementar os três construtores. Envolva exceções de baixo nível em específicas de domínio para abstrair detalhes de implementação. Nunca capture e engula exceções silenciosamente — no mínimo logue-as. Use manipuladores globais de exceção (AppDomain.UnhandledException, Application.ThreadException) como rede de segurança.
' 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 e Event Log
O Windows Event Log é ideal para eventos de nível de sistema (início/parada de serviço, erros críticos) — exige admin para criar a origem. Para log de aplicação, use um framework de logging estruturado: Serilog (moderno, friendly a JSON), NLog ou log4net. Logging estruturado (logger.Information("User {UserId}...", id)) preserva tipos de dados para consulta, ao contrário da concatenação de strings. Níveis de log (Debug, Info, Warning, Error, Fatal) permitem filtrar em runtime. Sempre inclua contexto (ID do usuário, ID do pedido) nos logs para depuração. Performance counters monitoram CPU, memória e métricas personalizadas em produção.
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)Testes Unitários e TDD
Testes unitários verificam métodos individuais isoladamente. MSTest (embutido), xUnit e NUnit são os principais frameworks. TestInitialize/TestCleanup executam antes/depois de cada teste. Métodos Assert verificam resultados (AreEqual, IsTrue, ThrowsException). DataTestMethod/DataRow parametrizam testes com múltiplas entradas. ExpectedException (ou Try/Catch/Assert.Fail) testa casos de exceção. Use Moq para mockar dependências (interfaces) — configure comportamento esperado e verifique chamadas. TDD (Test-Driven Development) escreve testes primeiro, depois código. Busque alta cobertura da lógica de negócio; pule getters/setters triviais. Execute testes em CI/CD para capturar regressões.
' 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)Coleções e Dicionários
ArrayList e List(Of T)
List(Of T) é a substituição moderna e type-safe do ArrayList. Sempre a prefira — você obtém verificação de tipo em tempo de compilação e melhor desempenho (sem boxing para tipos de valor). A cláusula From inicializa a lista inline. FindAll/Find usam predicados (lambdas). AddRange anexa múltiplos itens de uma vez.
' 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) é o hash map padrão. Use TryGetValue para obter um valor com segurança (retorna False se ausente, sem exceção). Add lança erro se a chave existe; o indexador (dict(key)) sobrescreve silenciosamente. As coleções Keys/Values permitem iterar ou extrair. LINQ funciona em dicionários — eles são 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 e SortedSet
HashSet é para teste rápido de pertinência e operações de conjunto (union, intersect, except). SortedSet mantém elementos em ordem ordenada (árvore de busca binária). Ambos têm operações O(1) ou O(log n) — muito mais rápidos que List.Contains (O(n)). Use HashSet quando a ordem não importa; SortedSet quando você precisa de iteração ordenada.
' 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 e Stack
Queue é FIFO (first-in-first-out) — use para agendamento de tarefas, BFS. Stack é LIFO (last-in-first-out) — use para undo, avaliação de expressões, DFS. Ambos têm enqueue/dequeue e push/pop O(1). Construa um Stack a partir de um array para invertê-lo em uma linha. Peek examina o próximo elemento sem removê-lo.
' 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}Coleções concorrentes
Coleções concorrentes são thread-safe — use-as em vez de bloquear coleções regulares. AddOrUpdate e GetOrAdd do ConcurrentDictionary são atômicos. ConcurrentQueue/Stack são lock-free. ConcurrentBag é mais rápido quando cada thread produz e consome principalmente seus próprios itens. BlockingCollection é o padrão padrão para filas producer-consumer.
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)E/S de Arquivos Avançada
Leitura e escrita de streams
Sempre envolva streams em blocos Using para garantir disposição (fecha o arquivo mesmo em exceções). File.ReadAllText/ReadAllLines são convenientes para arquivos pequenos. Para arquivos grandes, use StreamReader linha por linha. E/S async (ReadToEndAsync) mantém UIs responsivas — nunca chame E/S de arquivo síncrona na thread de UI.
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 UsingBinário e CSV
BinaryWriter/Reader lidam com dados binários tipados — ReadInt32/ReadDouble devem corresponder exatamente à ordem de escrita. TextFieldParser (em Microsoft.VisualBasic.FileIO) é a forma robusta de analisar CSV — lida com campos citados com vírgulas e newlines incorporados. Não crie seu próprio analisador CSV com Split(","). Para CSV complexo, considere o pacote NuGet CsvHelper.
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)Operações de path
Sempre use Path.Combine (não concatenação de strings) para juntar caminhos — ele lida com separadores corretamente. Path.GetInvalidFileNameChars ajuda a sanitizar entrada do usuário para nomes de arquivos. FileInfo/DirectoryInfo armazenam em cache metadados de arquivo — use-os quando precisar de múltiplas propriedades. SearchOption.AllDirectories recursa; para árvores enormes, 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())Monitoramento de arquivos
FileSystemWatcher monitora um diretório em busca de mudanças. Defina NotifyFilter para limitar quais mudanças disparam eventos (desempenho). A maior armadilha: editores frequentemente disparam múltiplos eventos Changed por salvamento (escrita, flush, metadados) — faça debounce rastreando o tempo do último evento. Erros (como estouro de buffer por muitos eventos) precisam de tratamento também.
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 SubSerialização
System.Text.Json é o serializador JSON moderno e de alto desempenho (substitui Newtonsoft.Json para código novo). XmlSerializer lida com XML — note que exige construtor sem parâmetros e propriedades públicas. Para coleções, serialise List(Of T) diretamente. JsonSerializerOptions com WriteIndented produz saída legível por humanos.
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)Tratamento de Erros Aprofundado
Padrões de Try/Catch
Capture exceções específicas antes das gerais (a ordem importa). Use Finally para limpeza — executa mesmo se você retornar ou lançar dentro do Try. Filtros When (VB 14+) permitem capturar com base em propriedades da exceção. Ao envolver exceções, passe a original como innerException para preservar o stack trace. Evite capturar Exception a menos que você re-lance.
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 TryExceções personalizadas
Exceções personalizadas devem herdar de Exception (ou uma base específica de domínio) e fornecer múltiplos construtores: apenas message, message + inner e específicas de domínio. Marque-as <Serializable> se puderem cruzar limites de AppDomain. Adicione propriedades para o contexto que chamadores precisam. Sobrescreva ToString para log claro.
' 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 TryEstratégias de tratamento de exceção
Nunca engula exceções silenciosamente — no mínimo, logue-as. Use Using para recursos IDisposable (mais limpo que try/finally). Registre manipuladores globais para AppDomain.UnhandledException (última chance) e Application.ThreadException (thread de UI do WinForms). AggregateException envolve múltiplas exceções de código paralelo — achate e trate cada 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 TryPadrões de Result (funcional)
O padrão Result retorna sucesso/falha como um valor em vez de lançar — útil para falhas esperadas (parsing, validação) onde exceções seriam caras e ruidosas. Combine com métodos de extensão (OnSuccess, OnFailure) para encadeamento. Use exceções para casos verdadeiramente excepcionais; Result para falhas previsíveis.
' 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)Integração de logging
Use um framework de logging (NLog, Serilog, log4net) — não Console.WriteLine. Níveis de log (Trace/Debug/Info/Warn/Error/Fatal) permitem filtrar em runtime via config. Logging estruturado (Serilog) preserva nomes de campos — melhor para busca em ferramentas como ELK ou Seq. Sempre inclua contexto (IDs de usuário, nomes de operação) nas mensagens de log.
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 TryBanco de Dados ADO.NET
Conexão e comandos
Sempre use parâmetros (nunca concatenação de strings) para prevenir injeção de SQL. Blocos Using garantem que conexões sejam fechadas mesmo em exceções. ExecuteReader para SELECT; ExecuteNonQuery para INSERT/UPDATE/DELETE; ExecuteScalar para um único valor (como COUNT ou SCOPE_IDENTITY). Connection pooling é automático — abra tarde, feche cedo.
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 UsingTransações
Transações garantem atomicidade — todas as operações têm sucesso ou todas falham. BeginTransaction em uma única conexão é para um banco de dados. TransactionScope lida com transações distribuídas entre múltiplas conexões (e até múltiplos bancos de dados) — chame Complete para commit. Escolha o nível de isolamento cuidadosamente: Serializable é o mais seguro, mas o mais lento; ReadCommitted é o padrão.
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 e DataAdapter
DataTable é uma tabela em memória — desconectada do banco de dados. SqlDataAdapter a preenche e envia atualizações de volta. SqlCommandBuilder gera automaticamente comandos INSERT/UPDATE/DELETE para cenários simples de tabela única. DataView filtra/ordena sem nova query. Para código novo, prefira ORMs (EF Core, Dapper) a DataTables brutos.
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 é o ponto ideal entre ADO.NET bruto (rápido, mas verboso) e EF Core (produtivo, mas mais lento). Ele mapeia colunas para propriedades automaticamente. Use objetos anônimos para parâmetros. Query<T> para listas; QuerySingle<T> para um; Execute para non-queries. Passe uma coleção para Execute para operações em lote. Multi-mapping lida com 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 UsingBásico de EF Core
EF Core é o ORM completo — lida com relacionamentos, migrações, rastreamento de mudanças e queries LINQ. Propriedades DbSet são os pontos de entrada. SaveChanges persiste todas as mudanças rastreadas. Include carrega antecipadamente entidades relacionadas (evita queries N+1). Para cenários somente leitura, use AsNoTracking() para melhor desempenho. Migrations gerenciam mudanças de schema.
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 em VB
Sintaxe de query
A sintaxe de query LINQ do VB é estilo SQL e frequentemente mais legível para queries complexas. From ... Where ... Select é o padrão básico. Múltiplas cláusulas From fazem cross joins (filtre com Where). Group By cria grupos acessíveis via palavra-chave Group. Join exige a palavra-chave Equals (não On ... Equals ... como 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.AmountSintaxe de method
A sintaxe de method usa métodos de extensão e lambdas — mais compacta e composável que a sintaxe de query. Function(x) ... é a sintaxe lambda no VB. First lança erro se vazio; FirstOrDefault retorna o padrão (Nothing para tipos de referência, 0 para Integer). Skip/Take é o padrão de paginação padrão. Ambas as sintaxes compilam para o mesmo 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()Agrupamento e agregação
GroupBy retorna IGrouping(Of Key, Element) — cada grupo tem uma Key e é ele mesmo enumerável. ToLookup cria uma tabela de lookup (como um dicionário multivalorado). Tipos anônimos (New With {Key .X = ...}) são ótimos para projeções — Key torna a propriedade imutável e parte da igualdade. Aggregate é o combinador mais flexível (mas menos legível).
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)Juntando dados
Join é inner join (apenas correspondências). Group Join com DefaultIfEmpty dá left join. Múltiplas cláusulas Join encadeiam naturalmente. O Join da sintaxe de method recebe quatro lambdas: chave externa, chave interna, seletor de resultado. Zip pareia elementos por posição — útil, mas raro. Para joins complexos, a sintaxe de query geralmente é mais clara que a de method.
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"}Execução deferida
LINQ usa execução deferida — queries executam quando iteradas, não quando definidas. Isso significa que cada For Each re-executa a query. Para armazenar resultados em cache, chame ToList/ToArray. Evite efeitos colaterais em lambdas de query (eles executam imprevisivelmente). Alguns operadores (Count, First, Any) forçam execução imediata. Any/All fazem 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 e async
Task.Run agenda trabalho em uma thread do thread pool. Async/Await faz código async parecer síncrono — o método pausa em Await sem bloquear a thread. Task.WhenAll espera por todas; Task.WhenAny retorna a primeira a terminar. Sempre passe CancellationToken para tarefas de longa duração e chame ThrowIfCancellationRequested periodicamente.
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 500msLoops Parallel
Parallel.For/ForEach particionam trabalho entre threads do thread pool — ótimo para loops CPU-bound. A sobrecarga thread-local é para reduções (sum, max) sem bloquear em cada iteração. state.Break para iterações superiores; state.Stop interrompe imediatamente. PLINQ (AsParallel) paraleliza LINQ — mas só ajuda para operações CPU-heavy.
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()Sincronização
SyncLock é a sincronização mais simples — exclusão mútua. Interlocked é para ops atômicos int/long (mais rápido que locks). Mutex pode ser nomeado e compartilhado entre processos (apps de instância única). Semaphore limita concorrência (ex.: máximo 3 conexões). ReaderWriterLockSlim permite múltiplos leitores OU um escritor — ótimo para caches de leitura intensa.
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 e producer-consumer
Channel<T> (moderno) é a forma recomendada de construir pipelines producer-consumer — totalmente async, suporta backpressure via capacidade limitada. ForEachAsync (consumo paralelo) permite que múltiplos consumidores leiam concorrentemente. BlockingCollection é o equivalente síncrono mais antigo. ConcurrentQueue/Stack são coleções lock-free para uso direto.
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 e agendamento
Três tipos de timer: System.Threading.Timer (thread pool, mais eficiente), System.Timers.Timer (cenários de servidor, eventos), Windows.Forms.Timer (thread de UI, para atualizações de UI). Para código async, Task.Delay é mais limpo que timers. Sempre descarte timers para prevenir vazamentos. Para trabalho async periódico, um loop While com Task.Delay e um CancellationToken é o padrão mais limpo.
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 FunctionSerialização
JSON com System.Text.Json
System.Text.Json é o serializador JSON moderno e rápido embutido no .NET. Atributos controlam a serialização: JsonPropertyName renomeia, JsonIgnore exclui. JsonNamingPolicy.CamelCase corresponde a convenções do JavaScript. Para JSON enorme, serialise para um stream (não uma string) para evitar carregar tudo na memória. JsonDocument analisa sem um tipo de destino.
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 UsingSerialização XML
XmlSerializer é verboso, mas flexível. Atributos controlam nomes de elementos/atributos e estrutura. Ele exige um construtor sem parâmetros e propriedades públicas de leitura/escrita. XmlArray/XmlArrayItem controlam coleções aninhadas. Para ler arquivos XML grandes, prefira XmlReader (streaming) a XmlSerializer (carrega tudo na memória).
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 lida com campos CSV citados corretamente. Para escrita, escape campos contendo vírgulas, aspas ou newlines envolvendo-os em aspas e duplicando aspas internas. Para produção, use CsvHelper (NuGet) — lida com todos os casos extremos, suporta mapeamento personalizado e é muito mais rápido. Sempre use uma biblioteca para CSV; o formato tem casos extremos surpreendentes.
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 UsingBinário e DataContract
DataContractSerializer é mais tolerante a versão que XmlSerializer (lida com campos adicionados/removidos via Order). Para serialização binária de alto desempenho, use Protocol Buffers (protobuf-net) ou MessagePack — ambos são 10-100x menores e mais rápidos que XML/JSON. Escolha com base nas necessidades: legível por humanos (JSON/XML) vs compacto/rápido (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)Conversores personalizados
Conversores personalizados lidam com tipos que System.Text.Json não suporta nativamente (DateOnly, TimeSpan, tipos personalizados). JsonDerivedType (NET 7+) lida com polimorfismo — serializa o tipo em runtime com um discriminador. Métodos ShouldSerialize* incluem propriedades condicionalmente (como o padrão do XmlSerializer). Sempre registre conversores em 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
Inspeção de tipo
Reflection permite inspecionar tipos em runtime. GetType(Type) para tipos em tempo de compilação; obj.GetType() para tipos em runtime. BindingFlags filtram membros (Public/NonPublic, Instance/Static). GetCustomAttributes retorna atributos aplicados a um tipo. Reflection é lento — armazene em cache objetos Type e MemberInfo para uso repetido.
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 IfCriando instâncias e invocando
Activator.CreateInstance cria objetos por tipo. MethodInfo.Invoke chama métodos — passe args como Object(). PropertyInfo.GetValue/SetValue acessam propriedades. BindingFlags.NonPublic permite acessar membros privados (use com moderação — quebra encapsulamento). MakeGenericMethod constrói um método genérico em runtime. Esta é a base de sistemas de plugins.
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)Atributos
Atributos personalizados adicionam metadados a tipos e membros. AttributeUsage controla onde podem ser aplicados e se múltiplos são permitidos. GetCustomAttribute/GetCustomAttributes os leem em runtime via reflection. Atributos embutidos: Obsolete (aviso/erro do compilador), Conditional (inclusão em tempo de compilação), Serializable. Atributos alimentam validação, mapeamento ORM, serialização e mais.
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 e código dinâmico
Reflection.Emit gera IL em runtime — poderoso, mas de baixo nível. DefineDynamicAssembly -> Module -> Type -> Method -> ILGenerator. Emit escreve opcodes IL. Árvores de expressão são uma alternativa de nível mais alto — construa expressões como dados, depois Compile para um delegate. Use Emit para desempenho extremo (serializadores personalizados); árvores de expressão para queries dinâmicas (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)) ' 8Considerações de desempenho
Reflection é 100-1000x mais lento que chamadas diretas. Sempre armazene em cache objetos MemberInfo. Para caminhos críticos, compile para delegates via árvores de expressão — quase tão rápido quanto chamadas diretas. Source generators (.NET moderno) podem fazer reflection em tempo de compilação, eliminando o custo de runtime inteiramente. Use reflection para frameworks (serializadores, ORMs, DI); evite-o na lógica de negócio.
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 (chamando DLLs Win32)
P/Invoke permite chamar funções de DLL nativas. DllImport especifica a biblioteca e as opções. CharSet.Unicode lida com marshaling de strings. ByRef para parâmetros de saída. StructLayout(Sequential) garante que structs correspondam ao layout C. SetLastError=True permite consultar o último erro Win32 via Marshal.GetLastWin32Error(). Sempre envolva chamadas nativas em uma classe NativeMethods.
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})")Interop COM
Interop COM permite chamar Office e outras bibliotecas COM. Adicione uma referência ao assembly de interop (ou use NuGet). SEMPRE libere objetos COM com Marshal.ReleaseComObject — eles não são coletados pelo garbage collector prontamente, levando a processos "fantasma" do Excel. Late binding (CreateObject) evita referências em tempo de compilação, mas perde IntelliSense e segurança de tipo.
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()Interop C#
VB e C# podem se chamar livremente — eles compilam para o mesmo IL. Adicione uma referência de projeto ou biblioteca compartilhada. Cuidado com distinção de maiúsculas/minúsculas (VB é case-insensitive), sintaxe de indexação e ref/out (VB usa ByRef). Recursos do C# como código unsafe, certos generics e pattern matching não têm equivalente no VB — use C# para essas partes.
' 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# libChamando Python e outras linguagens
Para interop Python, Python.NET embute CPython no .NET — acesso direto a objetos. Mais simples: faça shell para python.exe com Process.Start. R.NET faz o mesmo para R. ClearScript embute V8 para JavaScript. Escolha com base na profundidade de integração: shell para scripts únicos; embutir para integração estreita. Cuidado com desempenho (chamadas cross-runtime são lentas).
' 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")Memória e ponteiros
Marshal.AllocHGlobal/FreeHGlobal alocam/liberam memória não gerenciada — sempre os pare em Try/Finally. Marshal.Copy move arrays entre memória gerenciada e não gerenciada. VB não suporta código unsafe (use C# para isso, ou use métodos Marshal). Span(Of T) (.NET moderno) fornece desempenho de ponteiro com segurança de memória — preferível a ponteiros brutos.
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)Snippets de Visual Basic relacionados
Copy-paste ready code for common tasks.
Variáveis e Tipos
Declarar variáveis com Dim e inferência de tipos.
Loops e Iteração
Loops For, For Each, While e Do em VB.
Procedimentos Sub e Function
Definir Sub (sem retorno) e Function (retorna valor).
Básico do Windows Forms
Criar uma aplicação WinForms simples.
E/S de Arquivos
Ler/escrever arquivos texto e usar My.Computer.FileSystem.
Tratamento de Erros (Try/Catch)
Tratamento estruturado de exceções em VB.
Coleções (List, Dictionary)
Coleções genéricas em VB.NET.
Expressões de Consulta LINQ
Consultar dados com sintaxe LINQ do VB.
Was this helpful?