Переменные, типы и операторы
Объявление переменных и встроенные типы
VB.NET статически типизирован с объявлениями Dim. Частые типы: String, Integer (32-бит), Long (64-бит), Double (64-бит float), Decimal (высокая точность для финансовых), Boolean, Date, Char. Используйте суффикс типа (D для Decimal, F для Single, L для Long) для принудительных литеральных типов. Option Explicit On форсирует объявление переменных (предотвращает опечатки). Option Infer On включает type inference с Dim без явных типов. Nullable-типы (Integer?) оборачивают value-типы, чтобы они могли хранить Nothing — полезно для БД и API.
' 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 ModuleКонстанты, Enum и Structures
Const объявляет константы времени компиляции (неявно Shared, нельзя изменить). Enum определяет именованные целочисленные константы — используйте [Enum].Parse для конвертации строк и CInt для числового значения. Скобочная нотация [Error] экранирует зарезервированные ключевые слова. Атрибут <Flags> помечает enum как bit field, позволяя комбинацию через Or/And — распространено для разрешений и опций. Всегда указывайте underlying type (As Integer) для flags, чтобы контролировать ширину битов.
' 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 EnumКонвертация типов и кастинг
Расширяющие конвертации (Integer в Double) неявные и безопасные. Сужающие (Double в Integer) требуют явного кастинга. CInt/CStr/CDate/CDec — функции конвертации VB (CType — generic-версия). DirectCast строжайший — работает только при точном совпадении runtime-типа. TryCast возвращает Nothing при сбое вместо выброса (только ссылочные типы). Всегда предпочитайте TryParse вместо Parse для пользовательского ввода, чтобы избежать исключений. IsNumeric/IsDate — удобные валидаторы. CInt округляет (banker's rounding), а Int() пола.
' 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 IfОператоры и выражения
VB использует \ для целочисленного деления и / для вещественного (в отличие от C#, где / и %). Mod — оператор остатка. VB использует = и для присваивания, и для сравнения равенства (контекст определяет). And/Or вычисляют оба операнда; AndAlso/OrElse короткозамкнутые (предпочтительны для производительности и избегания null references). & — оператор конкатенации строк (не +, который может делать числовое сложение). Используйте AndAlso/OrElse по умолчанию для короткого замыкания и избегания ошибок вроде проверки 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}" ' interpolationВвод, вывод и форматирование
Console.WriteLine/Write — базовые методы вывода. Строковая интерполяция ($"...") — современный, читаемый способ встраивать выражения — поддерживает спецификаторы формата вроде :F2 (2 знака), :C (валюта), :P (процент), :X (hex). String.Format использует позиционные плейсхолдеры {0}, {1} с опциональным выравниванием ({0,-10} = по левому краю, ширина 10). Для циклов, строящих большие строки, используйте StringBuilder (не &), чтобы избежать создания многих промежуточных строк. Integer.Parse выбрасывает на невалидном вводе; используйте TryParse для безопасности.
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 ModuleУправляющий поток
If...Then...Else и тернарный
If...Then...ElseIf...Else — условие VB. AndAlso/OrElse короткозамкнутые (не вычисляют правую сторону, если не нужно) — всегда предпочитайте их And/Or для условий. Функция If() (VB 14+) — тернарный оператор: If(condition, trueVal, falseVal). If() с двумя аргументами — оператор null-coalescing: If(maybeNull, defaultValue). Избегайте старой функции IIf() — она всегда вычисляет обе ветви и возвращает Object (boxing). Однострочный If не требует 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 — switch VB — чище, чем цепочки If...ElseIf. Case поддерживает несколько значений (через запятую), диапазоны (To) и операторы сравнения (Is >= 90). Case Else — ветвь по умолчанию. В отличие от switch C#, VB не проваливается — каждый Case работает независимо и Break не нужен. Select Case работает со строками, числами и даже объектами (через Is). Идеально для диспетчеризации по дискретным значениям.
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 и итераторы
For...To...Next итерирует диапазон с опциональным Step (отрицательный для обратного отсчёта). For Each итерирует любой IEnumerable — чище для коллекций. Exit For ломает цикл; Continue For пропускает к следующей итерации. Iterator-функции (с Yield) производят последовательности лениво — значения генерируются по требованию, что эффективно по памяти для больших или бесконечных последовательностей. Yield возвращает одно значение, затем возобновляет с того же места на следующей итерации. Это эквивалент yield return в 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 и While
Do While выполняется, пока условие True; Do Until — пока условие False (пока не станет True). Размещение условия внизу (Loop While/Until) гарантирует, что тело выполнится хотя бы один раз — полезно для меню и валидации ввода. While...End While — старая, более простая форма. Exit Do ломает; Continue Do пропускает к следующей итерации. Выбирайте Do While, когда можете не войти в цикл; Do...Loop While, когда нужно выполниться хотя бы раз.
' Do While: test at top (may never run)
Dim count As Integer = 0
Do While count < 3
Console.WriteLine(count) ' 0 1 2
count += 1
Loop
' Do Until: runs UNTIL condition is true
count = 0
Do Until count >= 3
Console.WriteLine(count) ' 0 1 2
count += 1
Loop
' Do...Loop While: test at bottom (runs at least once)
count = 0
Do
Console.WriteLine(count)
count += 1
Loop While count < 3
' Do...Loop Until: test at bottom, runs until true
count = 0
Do
Console.WriteLine(count)
count += 1
Loop Until count >= 3
' While...End While (older syntax)
Dim i As Integer = 0
While i < 3
Console.WriteLine(i)
i += 1
End While
' Exit Do / Continue Do
Dim n As Integer = 0
Do While n < 100
n += 1
If n Mod 2 = 0 Then Continue Do ' skip even
If n > 10 Then Exit Do ' break
Console.WriteLine(n) ' 1 3 5 7 9
LoopWith...End With и GoTo
With...End With — синтаксический сахар для доступа к нескольким членам одного объекта без повторения имени переменной — полезно для инициализации и билдеров. GoTo прыгает к метке; современный VB не рекомендует его, кроме выхода из глубоко вложенных циклов или в устаревшей обработке On Error GoTo. On Error GoTo — обработка ошибок в стиле VB6 (Resume Next пропускает строку с ошибкой) — предпочитайте структурированный Try/Catch в новом коде. Объект Err хранит описание и номер последней ошибки.
' 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 NextПроцедуры, функции и события
Основы Sub и Function
Sub выполняет действие (без возвращаемого значения); Function возвращает значение. Return выходит из функции со значением. Старый стиль «присвоить имени функции» (Multiply = a * b) всё ещё работает, но Return яснее. При вызове Sub скобки опциональны (Greet "Alice" или Greet("Alice")). Функции, вызываемые ради возвращаемого значения, требуют скобок. По умолчанию VB позволяет вызывать функции без присвоения возвращаемого значения, но это может путать — будьте явны.
' 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)Параметры: ByVal, ByRef, Optional, ParamArray
ByVal (по умолчанию) передаёт копию — изменения внутри не влияют на вызывающего. ByRef передаёт ссылку — изменения ВЛИЯЮТ на вызывающего (как ref в C#). Используйте ByRef, когда процедура должна модифицировать переменную вызывающего или возвращать несколько значений. Optional-параметры имеют значения по умолчанию и должны идти после обязательных. ParamArray принимает переменное число аргументов (как params в C#) и должен быть последним. Именованные аргументы (name:=value) улучшают читаемость и позволяют пропускать optional.
' 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)Лямбда-выражения и делегаты
Лямбды — inline-функции: Function(...) для возвращающих значение, Sub(...) для не возвращающих. Func(Of T, TResult) — встроенный тип делегата для функций; Action(Of T) — для sub (без возврата). AddressOf создаёт делегат из именованного метода. Лямбды необходимы для LINQ (Where, Select, OrderBy принимают функции как аргументы). Многострочные лямбды используют Function...End Function (или Sub...End Sub). Делегаты — типобезопасные указатели на функции — полезны для колбэков, событий и паттернов стратегии.
' 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")События и обработчики событий
События включают паттерн наблюдателя: класс вызывает события, а подписчики обрабатывают их. Объявляйте через Event, запускайте через RaiseEvent, обрабатывайте через Handles (WithEvents, время компиляции) или AddHandler (runtime, динамически). WithEvents + Handles декларативны, но переменная должна быть на уровне модуля/класса. AddHandler/RemoveHandler позволяют подписываться/отписываться во время выполнения — полезно для динамического связывания. Обработчики событий должны соответствовать сигнатуре события. События — основа Windows Forms и WPF (клики кнопок, загрузка формы и т.д.).
' 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 ModuleПерегрузка, рекурсия и Shared-члены
Перегрузка позволяет нескольким методам иметь одно имя с разными списками параметров — компилятор выбирает лучшее совпадение. Ключевое слово Overloads требуется при перегрузке через наследование. Рекурсия — когда функция вызывает себя — нужен base case для завершения. Остерегайтесь stack overflow при глубокой рекурсии. Shared (C# static) члены принадлежат типу, а не экземплярам — вызывайте через ClassName.Member без создания объекта. Shared-конструкторы запускаются один раз на тип, идеальны для инициализации статических данных.
' 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 ClassСтроки и обработка текста
Основы строк и неизменяемость
Строки в VB.NET неизменяемы — каждый метод (ToUpper, Replace и т.д.) возвращает НОВУЮ строку; оригинал не меняется. Используйте = для сравнения равенства (VB-специфично). IndexOf возвращает первый индекс подстроки (-1 если не найдена). Split разбивает строку в массив; Join объединяет массив в строку. Trim удаляет пробелы (или указанные символы) с обоих концов. Для сравнения без учёта регистра используйте StringComparison.OrdinalIgnoreCase. Для тяжёлых строковых манипуляций в циклах используйте 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 и производительность
String неизменяема — каждый & или += создаёт новую строку, копируя всё предыдущее содержимое. В циклах это O(n²). StringBuilder изменяема — Append/Insert/Remove модифицируют тот же буфер, делая её O(n). Всегда используйте StringBuilder для циклов, строящих большие строки. StringBuilder цепочка (Append возвращает тот же экземпляр). Устанавливайте начальный Capacity, если знаете примерный размер, чтобы избежать перераспределений. Для разовых конкатенаций (не в циклах) & нормален и читаемее.
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 capacityСтроковая интерполяция и форматирование
Строковая интерполяция ($"...") — современный способ встраивать переменные и выражения — читаемая и поддерживает спецификаторы формата после двоеточия (:C валюта, :F2 фиксированные знаки, :N разделители тысяч, :X hex, :D5 с нулями, :P процент). Форматы дат: yyyy-MM-dd, HH:mm:ss, dddd (полное имя дня). Используйте String.Format для позиционных плейсхолдеров или когда строка формата строится динамически. Для culture-aware приложений (интернационализация) передавайте CultureInfo в ToString/Format для управления разделителями, символами валюты и форматами дат.
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 €Регулярные выражения
Regex.IsMatch проверяет совпадение; Regex.Matches находит все; Regex.Match находит первое. Группы захватывают части совпадения скобками — доступ через Groups(1), Groups(2). Regex.Replace подставляет совпадения (используйте $1, $2 для ссылок на группы). RegexOptions.Compiled компилирует паттерн для более быстрого повторного сопоставления. Частые паттерны: \d (цифра), \w (словесный символ), \s (пробел), + (один или более), * (ноль или более), {n} (точно n), ^/$ (начало/конец). Всегда валидируйте пользовательский ввод regex для email, телефонов и т.д.
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).CountОперации с Char и кодирование
Char — один Unicode-символ (2 байта). Используйте суффикс c для char-литералов ("A"c). Методы Char (IsDigit, IsLetter, IsUpper) полезны для валидации. Asc/Chr конвертируют между char и ASCII-кодами. ToCharArray конвертирует строку в изменяемый char-массив (сами строки неизменяемы). Encoding.UTF8.GetBytes конвертирует строки в byte-массивы (необходимо для файлового I/O и сетей). Base64 (Convert.ToBase64String) кодирует бинарные данные как текст — используется в data URI, email-вложениях и API.
' 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))Массивы и коллекции
Массивы: объявление, индексация и методы
Массивы VB 0-индексированы и фиксированного размера. Dim arr(n) создаёт n+1 элементов (0 to n). Литералы массивов используют { }. Многомерные массивы (,) прямоугольные; jagged-массивы ()() — массивы массивов (каждая строка может иметь разную длину). ReDim изменяет размер массива — Preserve сохраняет существующие значения (без него значения очищаются). Array.Sort/Reverse/Clear/IndexOf/Copy — статические помощники. Используйте List(Of T) вместо массивов, когда размер часто меняется.
' 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): динамические массивы
List(Of T) — основная динамическая коллекция — растёт автоматически при Add. Count даёт число элементов (не capacity). Add/Insert/Remove/RemoveAt/Contains/IndexOf — основные методы. Find/FindAll/Exists принимают предикат (лямбду) для кастомного поиска. Sort может принимать делегат Comparison для кастомного упорядочивания. List(Of T) оборачивает массив внутри и изменяет размер при заполнении (амортизированный O(1) Add). Конвертируйте в массив через ToArray(), когда нужна коллекция фиксированного размера.
Imports System.Collections.Generic
' create and initialize
Dim nums As New List(Of Integer) From {1, 2, 3}
Dim names As New List(Of String)
' add and insert
nums.Add(4) ' add to end
nums.AddRange({5, 6, 7}) ' add multiple
nums.Insert(0, 0) ' insert at index
Console.WriteLine(nums.Count) ' 8
' access and modify
Console.WriteLine(nums(0)) ' 0 (indexer)
nums(0) = 100 ' modify
' remove
nums.Remove(3) ' remove by value (first match)
nums.RemoveAt(0) ' remove by index
nums.RemoveRange(0, 2) ' remove 2 starting at index 0
nums.Clear() ' remove all
' search
nums = New List(Of Integer) From {1, 2, 3, 4, 5}
Console.WriteLine(nums.Contains(3)) ' True
Console.WriteLine(nums.IndexOf(3)) ' 2
Dim found As Boolean = nums.Exists(Function(n) n > 3)
Dim first As Integer = nums.Find(Function(n) n > 3) ' 4
Dim all As List(Of Integer) = nums.FindAll(Function(n) n > 2) ' 3,4,5
' iterate
For Each n As Integer In nums
Console.WriteLine(n)
Next
' convert to array
Dim arr() As Integer = nums.ToArray()
' sort and reverse
nums.Sort()
nums.Reverse()
' List with custom objects
Dim people As New List(Of (Name As String, Age As Integer))
people.Add(("Alice", 30))
people.Add(("Bob", 25))
people.Sort(Function(a, b) a.Age.CompareTo(b.Age)) ' sort by ageDictionary, HashSet и Queue/Stack
Dictionary(Of TKey, TValue) отображает ключи в значения — O(1) поиск по ключу. Используйте TryGetValue, чтобы избежать исключений и двойных lookup (ContainsKey + indexer). Итерируйте через KeyValuePair. HashSet(Of T) хранит уникальные элементы с O(1) add/contains — используйте для дедупликации и set-операций (UnionWith, IntersectWith, ExceptWith). Queue — FIFO (Enqueue/Dequeue); Stack — LIFO (Push/Pop). Все эти коллекции generic (типобезопасные, без boxing). Выбирайте по паттерну доступа: lookup → Dictionary, уникальность → HashSet, порядок → Queue/Stack.
Imports System.Collections.Generic
' Dictionary: key-value pairs
Dim ages As New Dictionary(Of String, Integer) From {
{"Alice", 30},
{"Bob", 25}
}
ages("Eve") = 28 ' add or update
ages.Add("Carol", 22) ' add (throws if key exists)
' access
Console.WriteLine(ages("Alice")) ' 30
Console.WriteLine(ages.ContainsKey("Bob")) ' True
Console.WriteLine(ages.ContainsValue(28)) ' True
' safe access (TryGetValue avoids double lookup)
Dim age As Integer
If ages.TryGetValue("Alice", age) Then
Console.WriteLine(age) ' 30
End If
' iterate
For Each kv As KeyValuePair(Of String, Integer) In ages
Console.WriteLine($"{kv.Key}: {kv.Value}")
Next
' remove
ages.Remove("Bob")
' HashSet: unique elements
Dim unique As New HashSet(Of Integer) From {1, 2, 3}
unique.Add(2) ' no effect (already exists)
unique.Add(4)
Console.WriteLine(unique.Count) ' 4
' set operations
Dim setA As New HashSet(Of Integer) From {1, 2, 3}
Dim setB As New HashSet(Of Integer) From {3, 4, 5}
setA.UnionWith(setB) ' {1,2,3,4,5}
setA.IntersectWith(setB) ' {3}
setA.ExceptWith(setB) ' remove setB from setA
' Queue: FIFO
Dim q As New Queue(Of String)
q.Enqueue("first")
q.Enqueue("second")
Console.WriteLine(q.Dequeue()) ' first
Console.WriteLine(q.Peek()) ' second (without removing)
' Stack: LIFO
Dim stk As New Stack(Of Integer)
stk.Push(1)
stk.Push(2)
Console.WriteLine(stk.Pop()) ' 2
Console.WriteLine(stk.Peek()) ' 1LINQ: Query и Method-синтаксис
LINQ (Language Integrated Query) трансформирует коллекции декларативно. Query-синтаксис (From...Where...Select) SQL-подобный и читаем для сложных запросов. Method-синтаксис (.Where().Select()) fluent и хорошо связывается. Оба компилируются в один код. Ключевые операторы: Where (фильтр), Select (трансформация), OrderBy (сортировка), GroupBy (группировка), Take/Skip (пагинация), First/FirstOrDefault (поиск), Any/All (проверка), Sum/Average/Max/Min (агрегация). FirstOrDefault возвращает default (0, Nothing), если нет совпадения — избегает исключений. LINQ работает на любом IEnumerable (массивы, списки, словари).
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-likeИнициализация коллекций и кортежи
Инициализаторы коллекций (From { }) создают и наполняют коллекции одним оператором. Кортежи (VB 2017+) группируют несколько значений — именованные поля (X, Y) яснее, чем Item1, Item2. Кортежи идеальны для возврата нескольких значений из функции без определения класса. Деконструкция (Dim (a, b) = tuple) разбивает кортеж в переменные. ToDictionary конвертирует список в словарь (селектор ключа + селектор значения). Кортежи — value-типы (struct), поэтому эффективны для небольших, временных группировок.
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")) ' 30Объектно-ориентированное программирование
Класс, поля и свойства
Классы связывают данные (поля/свойства) и поведение (методы). Auto-implemented properties (Property X As Type) авто-генерируют скрытое backing-field — лаконично для простых данных. Полные свойства (блоки Get/Set) позволяют валидацию, вычисления или побочные эффекты. ReadOnly-свойства имеют только Get. Me ссылается на текущий экземпляр (как this в C#). Конструкторы (Sub New) инициализируют объекты. Свойства выглядят как поля для вызывающих (p.Name), но выполняют код — эта инкапсуляция — ключевая выгода ООП.
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)Наследование и MyBase
Inherits устанавливает отношение IS-A (Dog IS Animal). MyBase.New() вызывает базовый конструктор (должен быть первым оператором). Overridable (база) + Overrides (производный) включают полиморфизм — производный метод запускается даже при доступе через базовую ссылку. Shadows (или Overloads) скрывает член базы вместо переопределения (без полиморфизма). Используйте Overridable/Overrides для истинного полиморфизма; Shadows только когда нужно скрыть член, который нельзя изменить. VB поддерживает одиночное наследование (один базовый класс), но множественные интерфейсы.
' 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)Интерфейсы и полиморфизм
Интерфейсы определяют контракт (методы, свойства) без реализации — классы их Implement. В отличие от наследования, класс может реализовать несколько интерфейсов. Ключевое слово Implements связывает член с его интерфейсным объявлением (VB-специфичный синтаксис). Интерфейсы включают полиморфизм: код может работать с любым IDrawable, не зная, Circle это или Square. TypeOf x Is T проверяет runtime-тип; DirectCast кастит (выбрасывает, если невалидно). Используйте интерфейсы для развязки кода: зависите от IDrawable, не Circle. Это принцип инверсии зависимостей.
' 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 ClassДженерики и ограничения
Дженерики (Of T) позволяют писать типобезопасный, переиспользуемый код для любого типа — без boxing, без кастов, проверка типов во время компиляции. List(Of T), Dictionary(Of K,V), Stack(Of T) — generic-коллекции. Ограничения ограничивают параметр типа: Class (ссылочный тип), Structure (value-тип), New (имеет конструктор без параметров — позволяет New T()), конкретный базовый класс или интерфейс. Дженерики избегают потери производительности на boxing (value-типы) и хрупкости кастов Object. Всегда предпочитайте generic-коллекции (List(Of T)) не-generic (ArrayList).
' generic class
Public Class Stack(Of T)
Private items As New List(Of T)
Public Sub Push(item As T)
items.Add(item)
End Sub
Public Function Pop() As T
If items.Count = 0 Then Throw New InvalidOperationException("Empty")
Dim last As T = items(items.Count - 1)
items.RemoveAt(items.Count - 1)
Return last
End Function
Public ReadOnly Property Count As Integer
Get
Return items.Count
End Get
End Property
End Class
' use with different types
Dim intStack As New Stack(Of Integer)
intStack.Push(1)
intStack.Push(2)
Console.WriteLine(intStack.Pop()) ' 2
Dim strStack As New Stack(Of String)
strStack.Push("Hello")
Console.WriteLine(strStack.Pop()) ' Hello
' generic method with constraint
Public Class Repository(Of T As {Class, New})
' T must be a reference type (Class) and have a parameterless constructor (New)
Private items As New List(Of T)
Public Sub Add(item As T)
items.Add(item)
End Sub
Public Function Find(match As Predicate(Of T)) As T
Return items.Find(match)
End Function
End Class
' generic function
Function FirstOrDefault(Of T)(list As IEnumerable(Of T), predicate As Func(Of T, Boolean)) As T
For Each item In list
If predicate(item) Then Return item
Next
Return Nothing ' default for reference types
End Function
' constraints:
' Class - reference type
' Structure - value type
' New - has parameterless constructor
' BaseClass - inherits from a specific class
' Interface - implements an interfacePartial-классы и пространства имён
Partial-классы разделяют класс по нескольким файлам — компилятор объединяет их. Полезно для отделения сгенерированного кода (designer-файлы) от рукописного или разделения больших классов. Пространства имён организуют типы и предотвращают коллизии имён (MyApp.Models.User vs MyApp.Services.User). Imports вводит имена пространств имён в scope (без полной квалификации). RootNamespace (настройка проекта) оборачивает все типы — установите в имя компании/продукта. Соглашения: пространства имён соответствуют структуре папок; одно пространство на логическую область.
' 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 ModuleОбработка ошибок и исключения
Try...Catch...Finally
Try/Catch/Finally — структурированная обработка исключений. Блоки Catch проверяются по порядку — специфичные исключения первыми, Exception (база) последним. Finally выполняется всегда (даже при Return или неперехваченных исключениях) — используйте для очистки (закрытие файлов, освобождение ресурсов). Throw (без аргументов) перебрасывает текущее исключение, сохраняя stack trace. When фильтрует условно (Catch...When condition). Свойства Exception: Message (описание), StackTrace (цепочка вызовов), Source (assembly). Никогда не ловите Exception молча без логирования — это скрывает баги.
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 TryГенерация и кастомные исключения
Throw New ExceptionType(...) выбрасывает исключение. Всегда передавайте осмысленное сообщение и используйте NameOf() для имён параметров (устойчиво к рефакторингу). При оборачивании исключений передавайте оригинал как innerException для сохранения причины. Кастомные исключения должны наследоваться от Exception (или более специфичной базы), быть помечены <Serializable> и предоставлять конструкторы (message, message+inner). Переопределяйте ToString() для кастомного отображения. Кастомные исключения позволяют вызывающим ловить специфичные типы ошибок и обрабатывать их соответственно — используйте для доменно-специфичных ошибок (InvalidTransactionException, PaymentFailedException).
' throw a built-in exception
Sub CheckAge(age As Integer)
If age < 0 Then
Throw New ArgumentOutOfRangeException(NameOf(age), "Age cannot be negative")
ElseIf age > 150 Then
Throw New ArgumentOutOfRangeException(NameOf(age), "Age unrealistic")
End If
End Sub
' throw with inner exception (wrapping)
Function LoadConfig(path As String) As String
Try
Return File.ReadAllText(path)
Catch ex As FileNotFoundException
Throw New ConfigurationException("Config not found", ex) ' wrap
End Try
End Function
' custom exception (inherit from Exception)
<Serializable>
Public Class InvalidTransactionException
Inherits Exception
Public Property TransactionId As String
Public Sub New(message As String, transactionId As String)
MyBase.New(message)
Me.TransactionId = transactionId
End Sub
Public Sub New(message As String, transactionId As String, inner As Exception)
MyBase.New(message, inner)
Me.TransactionId = transactionId
End Sub
Public Overrides Function ToString() As String
Return $"Transaction {TransactionId} failed: {Message}"
End Function
End Class
' using the custom exception
Sub ProcessPayment(amount As Decimal, txnId As String)
If amount <= 0 Then
Throw New InvalidTransactionException("Amount must be positive", txnId)
End If
' ... process
End Sub
' catch custom exception
Try
ProcessPayment(-100, "TXN-001")
Catch ex As InvalidTransactionException
Console.WriteLine($"Failed: {ex.TransactionId} - {ex.Message}")
End TryUsing-операторы и IDisposable
Using гарантирует вызов Dispose() при выходе из блока — критично для ресурсов (файлы, соединения с БД, сетевые потоки), которые не garbage-collected своевременно. Несколько ресурсов можно объявить в одном Using (через запятую). Реализуйте IDisposable, когда ваш класс держит unmanaged-ресурсы или другие IDisposable-объекты. Паттерн Dispose: Dispose(disposing As Boolean) освобождает managed (при disposing=True) и unmanaged ресурсы; GC.SuppressFinalize предотвращает запуск финализатора. Всегда оборачивайте IDisposable-объекты в Using для предотвращения утечек ресурсов.
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 UsingЛучшие практики исключений
Лучшие практики исключений: (1) Не ловите исключения, которые не можете обработать — позвольте им распространяться. (2) Никогда не глотайте исключения молча (пустой Catch) — как минимум логируйте. (3) Используйте TryParse вместо Parse для пользовательского ввода (исключения дороги и для действительно исключительных случаев). (4) Выбрасывайте специфичные типы исключений (ArgumentOutOfRangeException, не Exception). (5) Используйте фильтры When для логирования без ловли (верните False, чтобы не ловить). (6) Перебрасывайте через Throw (без аргументов) для сохранения stack trace — не Throw ex, который его сбрасывает. Исключения для исключительных условий, не для нормального потока управления.
' 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 FunctionОтладка и диагностика
Debug.Assert/WriteLine работают только в Debug-сборках (вырезаются в Release) — используйте для инвариантов и диагностики. Trace работает в обеих сборках — для production-логирования. #If DEBUG...#End If включает условную компиляцию. Debugger.Break() действует как программная точка останова. Stopwatch точно измеряет прошедшее время (для бенчмаркинга). StackTrace захватывает цепочку вызовов (полезно для логирования). EventLog пишет в Windows Event Log (требует admin для создания источника). Используйте эти инструменты для диагностики без модификации production-поведения.
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}")Файловый I/O и потоки
Чтение и запись текстовых файлов
File.WriteAllText/ReadAllText простейшие для малых файлов. File.ReadAllLines возвращает String-массив (загружает весь файл в память). Для больших файлов используйте StreamReader/StreamWriter с Using — они читают/пишут построчно, держа память низкой. Using гарантирует закрытие потока даже при исключениях. AppendAllText/AppendAllLines добавляют к существующим файлам. Всегда проверяйте File.Exists перед чтением для дружелюбной ошибки. Для async I/O (неблокирующий UI) используйте ReadAllTextAsync/WriteAllTextAsync с 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 FunctionБинарные файлы и сериализация
BinaryWriter/Reader читают/пишут примитивные типы в компактном бинарном формате — порядок чтения должен совпадать с записью. Для структурированных данных предпочитайте JSON (человекочитаемый, языконезависимый). System.Text.Json (встроенный, быстрый) или Newtonsoft.Json (популярный, богатый функционалом) сериализуют объекты в/из JSON. JsonSerializer.Serialize/Deserialize(Of T) — основные методы. Используйте JsonSerializerOptions для форматирования (indented, camelCase). JSON идеален для конфигов, API и обмена данными. Избегайте BinaryFormatter (риск безопасности — удалён в .NET 5+).
Imports System.IO
Imports System.Runtime.Serialization.Formatters.Binary
' write binary data
Using fs As New FileStream("data.bin", FileMode.Create)
Using writer As New BinaryWriter(fs)
writer.Write(42) ' Integer
writer.Write(3.14) ' Double
writer.Write("Hello") ' String (length-prefixed)
writer.Write(True) ' Boolean
writer.Write(New Byte() {1, 2, 3}) ' byte array
End Using
End Using
' read binary data (must read in same order!)
Using fs As New FileStream("data.bin", FileMode.Open)
Using reader As New BinaryReader(fs)
Dim n As Integer = reader.ReadInt32()
Dim d As Double = reader.ReadDouble()
Dim s As String = reader.ReadString()
Dim b As Boolean = reader.ReadBoolean()
Dim bytes() As Byte = reader.ReadBytes(3)
Console.WriteLine($"{n}, {d}, {s}, {b}")
End Using
End Using
' JSON serialization (Newtonsoft.Json or System.Text.Json)
Imports System.Text.Json
Public Class User
Public Property Name As String
Public Property Age As Integer
End Class
Dim u As New User With {.Name = "Alice", .Age = 30}
Dim json As String = JsonSerializer.Serialize(u)
' {"Name":"Alice","Age":30}
File.WriteAllText("user.json", json)
Dim restored As User = JsonSerializer.Deserialize(Of User)(json)
' JSON with options
Dim opts As New JsonSerializerOptions With {
.WriteIndented = True,
.PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}
Dim prettyJson = JsonSerializer.Serialize(u, opts)Операции с каталогами и путями
Directory.CreateDirectory/GetFiles/GetDirectories/Exists/Delete управляют папками. GetFiles поддерживает search patterns (*.csv) и SearchOption.AllDirectories для рекурсии. Класс Path безопасно обрабатывает пути кросс-платформенно — всегда используйте Path.Combine (не &) для объединения путей (обрабатывает разделители). Path.GetTempFileName создаёт уникальный temp-файл. FileInfo/DirectoryInfo — объектно-ориентированные обёртки над файловыми/каталожными операциями со свойствами (Length, CreationTime) и методами (CopyTo, MoveTo, Delete). Используйте их вместо строковых манипуляций для путей.
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)
NextПарсинг CSV и файлы данных
Для простого CSV Split(","c) работает — но ломается на quoted-полях с запятыми ("Smith, John"). TextFieldParser (в Microsoft.VisualBasic.FileIO) корректно обрабатывает кавычки, экранированные кавычки и разделители — используйте для реального CSV. Также парсит файлы фиксированной ширины (FieldWidths). Для записи CSV вручную заключайте в кавычки поля, содержащие запятые/кавычки. My.Computer.FileSystem — VB-специфичная удобная обёртка (проще API, меньше контроля). Для больших CSV рассмотрите CsvHelper (NuGet) — робастная, стриминговая CSV-библиотека. Всегда обрабатывайте строку заголовка отдельно.
Imports System.IO
Imports Microsoft.VisualBasic.FileIO
' simple CSV split (doesn't handle quoted commas)
Dim lines() As String = File.ReadAllLines("data.csv")
For Each line In lines
Dim fields() As String = line.Split(","c)
Console.WriteLine($"{fields(0)}, {fields(1)}")
Next
' robust CSV with TextFieldParser (handles quotes, commas)
Using parser As New TextFieldParser("data.csv")
parser.TextFieldType = FieldType.Delimited
parser.Delimiters = New String() {","}
parser.HasFieldsEnclosedInQuotes = True
' skip header
If Not parser.EndOfData Then parser.ReadLine()
While Not parser.EndOfData
Dim fields() As String = parser.ReadFields()
Console.WriteLine($"Name: {fields(0)}, Age: {fields(1)}")
End While
End Using
' write CSV
Using writer As New StreamWriter("output.csv")
writer.WriteLine("Name,Age,City")
writer.WriteLine("Alice,30,NYC")
writer.WriteLine("Bob,25,LA")
' quote fields with commas
writer.WriteLine($"""Smith, John"",40,Chicago")
End Using
' parse fixed-width files
Using parser As New TextFieldParser("fixed.txt")
parser.TextFieldType = FieldType.FixedWidth
parser.FieldWidths = New Integer() {10, 5, 20}
End Using
' read with My.Computer.FileSystem (VB-specific shortcut)
Dim contents As String = My.Computer.FileSystem.ReadAllText("data.txt")
Dim allLines As Object = My.Computer.FileSystem.ReadAllBytes("data.bin")
' write bytes
File.WriteAllBytes("data.bin", New Byte() {1, 2, 3, 4, 5})Async файловые операции
Async файловый I/O использует ReadToEndAsync/ReadLineAsync/WriteAsync с Await — поток не блокируется во время I/O, улучшая отзывчивость (особенно в UI-приложениях). Async-методы возвращают Task или Task(Of T); Await разворачивает результат. Для обработки многих файлов конкурентно запустите все задачи и Await Task.WhenAll (параллельный I/O). Async Sub — для обработчиков событий; Async Function — для всего остального. Async I/O блестит на web-серверах (много запросов) и desktop-приложениях (удержание UI отзывчивым). Накладные расходы малы, поэтому используйте async для любого I/O, которое может быть медленным.
Imports System.IO
Imports System.Threading.Tasks
' async read (non-blocking)
Async Function ReadFileAsync(path As String) As Task(Of String)
Using reader As New StreamReader(path)
Return Await reader.ReadToEndAsync()
End Using
End Function
' async write
Async Function WriteFileAsync(path As String, content As String) As Task
Using writer As New StreamWriter(path)
Await writer.WriteAsync(content)
End Using
End Function
' async line-by-line (memory-efficient for large files)
Async Function ProcessLargeFileAsync(path As String) As Task
Using reader As New StreamReader(path)
Dim line As String
Do
line = Await reader.ReadLineAsync()
If line Is Nothing Then Exit Do
' process line
Console.WriteLine(line)
Loop
End Using
End Function
' call async methods (must be in an Async Sub/Function)
Async Sub Main()
Dim content As String = Await ReadFileAsync("data.txt")
Console.WriteLine(content)
Await WriteFileAsync("output.txt", "Async write")
End Sub
' parallel file processing
Async Function ProcessFilesAsync(paths As String()) As Task
Dim tasks As New List(Of Task)
For Each path In paths
tasks.Add(ProcessFileAsync(path))
Next
Await Task.WhenAll(tasks) ' wait for all
End Function
Async Function ProcessFileAsync(path As String) As Task
Dim content = Await File.ReadAllTextAsync(path)
' process content
End Function
' File.ReadAllLinesAsync (.NET Core+)
Async Function CountLinesAsync(path As String) As Task(Of Integer)
Dim lines = Await File.ReadAllLinesAsync(path)
Return lines.Length
End FunctionWindows Forms и UI
Основы форм и события
Windows Forms (WinForms) строит desktop-UI добавлением Controls на Form. Каждый контрол имеет свойства (Text, Location, Size) и события (Click, Load, FormClosing). AddHandler связывает события во время выполнения; Handles (с WithEvents) — во время компиляции. MessageBox.Show отображает диалоги. OnLoad/OnFormClosing — переопределяемые protected-методы для жизненного цикла формы. Application.Run запускает цикл сообщений. WinForms использует дизайнер (drag-and-drop в Visual Studio) — сгенерированный код идёт в .Designer.vb partial-класс. Для современных приложений рассмотрите WPF или 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 ModuleРаспространённые контролы
Частые WinForms-контролы: TextBox (текстовый ввод, Multiline для области), CheckBox (булев), RadioButton (взаимоисключающие — группируйте в GroupBox), ComboBox (dropdown), NumericUpDown (числовой ввод со спиннером), ListBox (выбираемый список). Устанавливайте Location (Point) и Size. Используйте object initializers (With {.Prop = value}) для лаконичной настройки. Controls.AddRange добавляет несколько контролов сразу. У каждого контрола есть событие по умолчанию (Button.Click, TextBox.TextChanged, ComboBox.SelectedIndexChanged) — двойной клик в дизайнере генерирует обработчик.
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: панели, docking и anchoring
Layout WinForms: Dock (Top/Bottom/Left/Right/Fill) заставляет контрол заполнять край — добавляйте Fill последним, чтобы он занял оставшееся пространство. Anchor привязывает контрол к краям родителя (он меняет размер при ресайзе формы). TableLayoutPanel упорядочивает контролы в сетке (строки/столбцы с процентными/абсолютными размерами) — лучше для форм. FlowLayoutPanel складывает контролы в поток (оборачивается автоматически). Panel — простой контейнер для группировки. Для адаптивных layout предпочитайте TableLayoutPanel ручному позиционированию. Порядок Controls.Add важен для docking (поздние добавления перекрывают ранние).
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 ClassДиалоги: OpenFileDialog, SaveFileDialog, ColorDialog
Частые диалоги: OpenFileDialog (выбрать файл для открытия), SaveFileDialog (выбрать, куда сохранить), ColorDialog (выбрать цвет), FontDialog (выбрать шрифт), FolderBrowserDialog (выбрать каталог). Filter использует формат "Description|pattern|Description|pattern". ShowDialog возвращает DialogResult (OK/Cancel) — всегда проверяйте перед использованием результата. Оборачивайте диалоги в Using (они IDisposable). OverwritePrompt предотвращает случайные перезаписи. InitialDirectory запускает диалог в полезной локации. Эти диалоги предоставляют нативный Windows UI без кастомного кода.
Imports System.Windows.Forms
Public Class DialogForm
Inherits Form
Private Sub OpenFile()
Using ofd As New OpenFileDialog
ofd.Title = "Select a file"
ofd.Filter = "Text files (*.txt)|*.txt|CSV files (*.csv)|*.csv|All files (*.*)|*.*"
ofd.FilterIndex = 1
ofd.Multiselect = False
ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
If ofd.ShowDialog() = DialogResult.OK Then
Dim path As String = ofd.FileName
MessageBox.Show($"Selected: {path}")
End If
End Using
End Sub
Private Sub SaveFile()
Using sfd As New SaveFileDialog
sfd.Filter = "Text files (*.txt)|*.txt|All files (*.*)|*.*"
sfd.DefaultExt = "txt"
sfd.AddExtension = True
sfd.OverwritePrompt = True ' ask before overwriting
If sfd.ShowDialog() = DialogResult.OK Then
System.IO.File.WriteAllText(sfd.FileName, "Saved content")
End If
End Using
End Sub
Private Sub PickColor()
Using cd As New ColorDialog
cd.AllowFullOpen = True
cd.FullOpen = True
cd.Color = Color.Red
If cd.ShowDialog() = DialogResult.OK Then
Me.BackColor = cd.Color
End If
End Using
End Sub
Private Sub PickFont()
Using fd As New FontDialog
fd.ShowColor = True
fd.Font = Me.Font
If fd.ShowDialog() = DialogResult.OK Then
Me.Font = fd.Font
End If
End Using
End Sub
Private Sub PickFolder()
Using fbd As New FolderBrowserDialog
fbd.Description = "Select a folder"
fbd.SelectedPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
If fbd.ShowDialog() = DialogResult.OK Then
MessageBox.Show($"Folder: {fbd.SelectedPath}")
End If
End Using
End Sub
End ClassData binding и DataGridView
Data binding автоматически связывает UI-контролы с data-объектами. Простая привязка: txt.DataBindings.Add("Text", obj, "PropName"). Для списков установите DataGridView.DataSource в BindingList(Of T) или DataTable. INotifyPropertyChanged включает two-way binding — UI обновляется при изменении свойства (вызывайте PropertyChanged в setter). BindingList(Of T) — как ObservableCollection — уведомляет сетку при добавлении/удалении элементов. BindingSource добавляет навигацию и фильтрацию. AutoGenerateColumns создаёт столбцы из свойств. Data binding устраняет ручной код синхронизации между UI и данными.
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 ClassФайловый I/O углублённо
StreamReader и StreamWriter
StreamReader/StreamWriter обрабатывают текстовые файлы с правильным кодированием и dispose. Блок Using гарантирует закрытие файлов даже при исключениях — всегда используйте. File.ReadAllLines/ReadAllText удобны для малых файлов; ReadLine в цикле эффективен по памяти для больших. Async-методы (ReadAllTextAsync) предотвращают зависание UI во время I/O. StreamWriter с append:=True добавляет к существующим файлам. Кодировка по умолчанию UTF-8; укажите явно через New StreamWriter(path, append, Encoding.UTF8) для legacy-форматов.
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")Бинарные и CSV-файлы
BinaryWriter/Reader хранят примитивные типы в компактном бинарном формате — намного меньше текста и быстрее парсится. Всегда читайте в том же порядке и типах, что писали. Для CSV TextFieldParser корректно обрабатывает quoted-поля с встроенными запятыми (в отличие от наивного Split). Для production CSV используйте библиотеку вроде CsvHelper (NuGet), обрабатывающую edge-кейсы, маппинг типов и стриминг. Бинарные файлы платформо-зависимы (endianness); используйте BinaryWriter с little-endian для кросс-платформенной совместимости.
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 UsingФайловые системные операции
File и FileInfo предоставляют файловые операции; Directory и DirectoryInfo обрабатывают папки. FileInfo/DirectoryInfo эффективнее, когда нужно несколько атрибутов (один системный вызов). EnumerateFiles ленивый (возвращает по одному) vs GetFiles, загружающий все пути в память — используйте Enumerate для больших каталогов. Path.Combine строит пути с правильными разделителями кросс-платформенно; никогда не конкатенируйте строки вручную. SearchOption.AllDirectories может выбросить на access-denied; ловите UnauthorizedAccessException или используйте рекурсивный помощник.
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 pathСериализация (JSON и XML)
System.Text.Json (современный, быстрый) предпочтительнее устаревшего DataContractJsonSerializer. JsonSerializer.Serialize/Deserialize обрабатывают большинство типов; используйте JsonSerializerOptions для форматирования и политик именования. Для XML XmlSerializer простой, но ограничен (нет словарей, требует конструктор без параметров). Для сложных сценариев (полиморфизм, циклические ссылки) рассмотрите Newtonsoft.Json (Json.NET) через NuGet — больше функций, но медленнее. Всегда обрабатывайте ошибки десериализации (JsonException) для непроверенного ввода.
Imports System.Text.Json
' define a class
Public Class Person
Public Property Name As String
Public Property Age As Integer
Public Property Email As String
End Class
' JSON serialize
Dim p As New Person With {.Name = "Alice", .Age = 30, .Email = "[email protected]"}
Dim json As String = JsonSerializer.Serialize(p)
File.WriteAllText("person.json", json)
' JSON with options
Dim opts As New JsonSerializerOptions With {
.WriteIndented = True,
.PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}
json = JsonSerializer.Serialize(p, opts)
' JSON deserialize
Dim text = File.ReadAllText("person.json")
Dim p2 = JsonSerializer.Deserialize(Of Person)(text)
' serialize a list
Dim people = New List(Of Person) From {p, p2}
json = JsonSerializer.Serialize(people)
' XML serialization
Imports System.Xml.Serialization
Dim xs As New XmlSerializer(GetType(Person))
Using fs As New FileStream("person.xml", FileMode.Create)
xs.Serialize(fs, p)
End Using
Using fs As New FileStream("person.xml", FileMode.Open)
Dim p3 = CType(xs.Deserialize(fs), Person)
End UsingAsync файловые операции и потоки
Async файловый I/O удерживает UI отзывчивым во время больших операций. ReadAsync/WriteAsync работают с буферами для тонкой отчётности о прогрессе. IProgress(Of T) сообщает прогресс в UI-поток безопасно. MemoryStream — для данных в памяти (без диска). GZipStream сжимает/разжимает потоки на лету. Для сетевого I/O используйте HttpClient (async). Всегда используйте блоки Using для гарантии закрытия потоков. Размер буфера 81920 (80 КБ) — хороший default — балансирует память и накладные расходы 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 UsingДоступ к БД с ADO.NET
Соединения и команды
Всегда используйте параметризованные запросы (Parameters.AddWithValue) для предотвращения SQL-инъекций — никогда не конкатенируйте пользовательский ввод в SQL-строки. SqlConnection, SqlCommand и SqlDataReader — все IDisposable; оборачивайте их в Using. ExecuteReader возвращает строки; ExecuteNonQuery возвращает затронутые строки (INSERT/UPDATE/DELETE); ExecuteScalar возвращает одно значение (COUNT, SUM). Для других БД используйте соответствующий провайдер (OracleConnection, OleDbConnection, OdbcConnection) или generic DbProviderFactory.
Imports System.Data.SqlClient ' or Microsoft.Data.SqlClient
' connection string (SQL Server)
Dim connStr = "Server=localhost;Database=MyDb;Integrated Security=True;"
' basic query
Using conn As New SqlConnection(connStr)
conn.Open()
Using cmd As New SqlCommand("SELECT Id, Name FROM Users WHERE Age > @age", conn)
cmd.Parameters.AddWithValue("@age", 25)
Using reader As SqlDataReader = cmd.ExecuteReader()
While reader.Read()
Dim id = reader.GetInt32(0)
Dim name = reader.GetString(1)
Console.WriteLine($"{id}: {name}")
End While
End Using
End Using
End Using ' connection auto-closed
' insert with parameters (prevent SQL injection!)
Using conn As New SqlConnection(connStr), _
cmd As New SqlCommand("INSERT INTO Users (Name, Age) VALUES (@name, @age)", conn)
cmd.Parameters.AddWithValue("@name", "Bob")
cmd.Parameters.AddWithValue("@age", 30)
conn.Open()
Dim rowsAffected = cmd.ExecuteNonQuery()
End Using
' scalar query (single value)
Using conn As New SqlConnection(connStr), _
cmd As New SqlCommand("SELECT COUNT(*) FROM Users", conn)
conn.Open()
Dim count = CInt(cmd.ExecuteScalar())
End UsingDataSet и DataTable
DataTable/DataSet — отключённые контейнеры данных — загрузите один раз, работайте offline, обновляйте позже. SqlDataAdapter.Fill загружает данные; SqlCommandBuilder авто-генерирует команды INSERT/UPDATE/DELETE для простых single-table-сценариев. Typed DataSets (через .xsd-дизайнер) предоставляют проверку типов во время компиляции и IntelliSense. DataView фильтрует и сортирует без модификации underlying DataTable. Для современных приложений предпочитайте Entity Framework или Dapper вместо сырых DataSet — они более поддерживаемы и тестируемы. DataSet остаются полезны для отчётности и legacy-интеропа.
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"))
NextТранзакции и пулинг соединений
Транзакции обеспечивают атомарность — все операции успешно или все неудачно. SqlTransaction оборачивает несколько команд на одном соединении. TransactionScope включает распределённые транзакции между несколькими соединениями/ресурсами (использует MS DTC при необходимости) — вызовите scope.Complete() для commit. Пулинг соединений автоматический в ADO.NET: соединения переиспользуются, а не пересоздаются, что резко повышает производительность. Всегда Close/Dispose соединения (блоки Using) для возврата в пул. Настраивайте размер пула и время жизни в строке подключения для high-throughput-приложений.
Imports System.Data.SqlClient
' explicit transaction
Using conn As New SqlConnection(connStr)
conn.Open()
Using tran As SqlTransaction = conn.BeginTransaction()
Try
Using cmd1 As New SqlCommand(
"UPDATE Accounts SET Balance = Balance - 100 WHERE Id = 1",
conn, tran)
cmd1.ExecuteNonQuery()
End Using
Using cmd2 As New SqlCommand(
"UPDATE Accounts SET Balance = Balance + 100 WHERE Id = 2",
conn, tran)
cmd2.ExecuteNonQuery()
End Using
tran.Commit()
Catch ex As Exception
tran.Rollback()
Throw
End Try
End Using
End Using
' transaction scope (distributed, multi-resource)
Imports System.Transactions
Using scope As New TransactionScope()
' multiple connections, even different databases
Using conn1 As New SqlConnection(connStr1)
' operations on DB1
End Using
Using conn2 As New SqlConnection(connStr2)
' operations on DB2
End Using
scope.Complete() ' commit all
End Using ' rollback if Complete not called
' connection pooling (automatic, configure in connection string)
' "Server=...;Pooling=True;Max Pool Size=100;Connection Lifetime=300"
' connections are reused — always Close/Dispose to return to poolAsync операции с БД
Async операции с БД (OpenAsync, ExecuteReaderAsync, ReadAsync) предотвращают зависание UI во время долгих запросов. Паттерн зеркалирует sync ADO.NET, но с Await. Всегда используйте Async в UI-приложениях для удержания отзывчивости. Для high-throughput-серверов async DB-вызовы освобождают потоки для обработки других запросов. MARS (Multiple Active Result Sets) позволяет несколько readers на одном соединении — включите через 'MultipleActiveResultSets=True' в строке подключения. Обрабатывайте SqlException для доменно-специфичных ошибок (deadlocks, нарушения ограничений).
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 — micro-ORM, расширяющий IDbConnection методами-расширениями — быстрый (почти raw ADO.NET) и простой. Query<T> мапит строки на объекты автоматически по совпадению имён столбцов со свойствами. Анонимные объекты предоставляют параметры (защита от SQL-инъекций). Bulk insert передаёт список, и Dapper выполняет по разу на элемент. Multi-mapping обрабатывает joins разделением строк по столбцу. Dapper идеален, когда нужен контроль SQL с меньшим boilerplate, чем raw ADO.NET. Для сложных графов объектов и отслеживания изменений используйте 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
Query и Method-синтаксис
LINQ предоставляет два синтаксиса: query (SQL-подобный, From...Where...Select) и method (fluent, .Where().Select()). Они компилируются в один IL — выбирайте по читаемости. Query-синтаксис поддерживает меньше операторов (нет Sum, Count напрямую); используйте method для них. LINQ использует отложенное выполнение — запрос не запускается, пока вы не итерируете (For Each) или не вызовете материализующий оператор (ToList, Count). Это значит, что модификация источника после определения запроса влияет на результаты. Используйте .ToList() для немедленного выполнения и снапшота данных.
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
NextАгрегация и группировка
Операторы агрегации LINQ (Sum, Average, Min, Max, Count, Any, All) сводят последовательности к одиночным значениям. GroupBy разделяет элементы по ключу; каждая группа имеет .Key и сама enumerable. Query-синтаксис 'Group By...Into Group' VB-специфичный. Несколько агрегатов на группу распространены для отчётности. Any/All короткозамкнутые (ранняя остановка) — полезны для проверки условий без полной итерации. Агрегатные функции выбрасывают на пустых последовательностях; используйте nullable-варианты (Sum возвращает 0, Average выбрасывает) или DefaultIfEmpty.
Dim people = New List(Of Person) From {
New Person With {.Name = "Al", .Dept = "Eng", .Salary = 90000},
New Person With {.Name = "Bo", .Dept = "Eng", .Salary = 70000},
New Person With {.Name = "Cy", .Dept = "Sales", .Salary = 60000}
}
' aggregation
Dim total = people.Sum(Function(p) p.Salary)
Dim avg = people.Average(Function(p) p.Salary)
Dim max = people.Max(Function(p) p.Salary)
Dim count = people.Count(Function(p) p.Dept = "Eng")
Dim any = people.Any(Function(p) p.Salary > 80000)
Dim all = people.All(Function(p) p.Salary > 0)
' grouping
Dim byDept = From p In people
Group p By p.Dept Into Group
Select Dept, Count = Group.Count(), Avg = Group.Average(Function(x) x.Salary)
For Each g In byDept
Console.WriteLine($"{g.Dept}: {g.Count} people, avg ${g.Avg}")
Next
' method syntax grouping
Dim grouped = people.GroupBy(Function(p) p.Dept)
For Each g In grouped
Console.WriteLine($"{g.Key}: {g.Count()} people")
Next
' group with multiple aggregates
Dim stats = people.GroupBy(Function(p) p.Dept).
Select(Function(g) New With {
.Dept = g.Key,
.Count = g.Count(),
.Total = g.Sum(Function(p) p.Salary),
.Min = g.Min(Function(p) p.Salary)
})Joining, Zip и set-операции
Join выполняет inner joins (совпадающие ключи); Group Join с DefaultIfEmpty создаёт left joins. Zip парит элементы позиция-в-позицию. Set-операции (Union, Intersect, Except, Distinct) используют компараторы равенства по умолчанию — переопределяйте Equals/GetHashCode или передавайте кастомный IEqualityComparer для сложных типов. Concat добавляет без удаления дубликатов (в отличие от Union). Все ленивые, кроме материализованных. Для больших датасетов рассмотрите HashSet или Dictionary для O(1) lookup вместо O(n*m) вложенных циклов 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,6Сортировка, пагинация и элементные операции
OrderBy/ThenBy сортируют (стабильно); OrderByDescending/ThenByDescending сортируют по убыванию. Несколько ключей сортировки используют ThenBy после OrderBy. Пагинация использует Skip (offset) и Take (limit) — необходима для больших датасетов в UI. First/Last выбрасывают на пустых; FirstOrDefault возвращает default (Nothing для ссылочных, 0 для чисел). Single форсирует ровно одно совпадение (иначе выброс) — полезно для валидации. TakeWhile/SkipWhile разделяют по предикату до его неудачи. Все ленивые, кроме First/Last/Single, форсирующих итерацию.
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 и деревья выражений
Методы-расширения (с <Extension()>) добавляют LINQ-подобные операторы к любому типу. Кастомные агрегаты (Median, Mode, StandardDeviation) заполняют пробелы в встроенных операторах. IEnumerable(Of T) использует делегаты (in-memory); IQueryable(Of T) использует деревья выражений, транслируемые в SQL (EF/LINQ to SQL) — смешивание может вызвать client-side evaluation (медленно). ToList/ToArray/ToDictionary форсируют выполнение и снапшот результатов. PLINQ (.AsParallel) параллелит запросы — используйте для CPU-bound операций на больших коллекциях, но остерегайтесь упорядочивания и потокобезопасности.
' 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()Многопоточность и async
Task и Async/Await
Async/Await — современный способ писать асинхронный код — выглядит синхронно, но не блокирует потоки. Помечайте методы Async и используйте Await на вызовах, возвращающих Task. UI-обработчики событий могут быть Async Sub (единственное место, где Async Sub приемлем). Task.Run выгружает CPU-bound-работу в thread pool. Task.WhenAll ждёт все задачи (параллельно); Task.WhenAny ждёт первую завершённую. Компилятор генерирует state machine, обрабатывающую continuations, распространение исключений и захват контекста. Всегда предпочитайте Async/Await ручному threading или колбэкам.
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)Потокобезопасность и синхронизация
SyncLock (VB) / lock (C#) обеспечивает взаимное исключение — только один поток входит в блок за раз. Лочьте на private Object, никогда на Me или string. Interlocked предоставляет атомарные операции без локов (быстрее для простых случаев). Mutex работает между процессами (single-instance-приложения). SemaphoreSlim ограничивает конкурентный доступ (например, максимум 3 API-вызова одновременно). Вссегда освобождайте локи/семафоры в блоках Finally. Deadlocks возникают, когда два потока ждут друг друга — захватывайте локи в согласованном порядке. Используйте ConcurrentDictionary, ConcurrentQueue для lock-free потокобезопасных коллекций.
Imports System.Threading
' lock (mutual exclusion)
Private ReadOnly _lock As New Object
Private _counter As Integer = 0
Sub Increment()
SyncLock _lock
_counter += 1
End SyncLock
End Sub
' Monitor (equivalent to SyncLock)
Monitor.Enter(_lock)
Try
_counter += 1
Finally
Monitor.Exit(_lock)
End Try
' Interlocked (atomic operations, no lock needed)
Interlocked.Increment(_counter)
Interlocked.Add(_counter, 10)
Interlocked.Exchange(_counter, 0)
Interlocked.CompareExchange(_counter, 1, 0) ' if 0, set to 1
' Mutex (cross-process)
Dim mutex As New Mutex(False, "GlobalMyAppMutex")
mutex.WaitOne()
Try
' critical section
Finally
mutex.ReleaseMutex()
End Try
' SemaphoreSlim (limit concurrent access)
Dim sem As New SemaphoreSlim(3) ' max 3 concurrent
Await sem.WaitAsync()
Try
' limited concurrency
Finally
sem.Release()
End Try
' CountdownEvent
Dim cde As New CountdownEvent(5)
Parallel.For(0, 5, Sub(i)
' work
cde.Signal()
End Sub)
cde.Wait()Parallel и BackgroundWorker
Parallel.For/ForEach распределяют работу по потокам thread pool — идеально для CPU-bound циклов. PLINQ (.AsParallel) параллелит LINQ-запросы. Используйте ParallelOptions.MaxDegreeOfParallelism для ограничения потоков. CancellationToken включает кооперативную отмену. BackgroundWorker — legacy, но удобен для WinForms — он маршалит ProgressChanged и RunWorkerCompleted в UI-поток автоматически. Для нового кода предпочитайте Task.Run + IProgress(Of T) + Async/Await. Параллельные циклы блокирующие (в отличие от Async); не используйте их на 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()Таймеры и маршалинг в UI-поток
В .NET три Timer: Windows.Forms.Timer (UI-поток, простейший для форм), System.Threading.Timer (thread pool, лёгкий) и System.Timers.Timer (компонент, серверные сценарии). Только Forms.Timer может обновлять UI напрямую; остальные требуют Invoke. Control.Invoke маршалит делегат в UI-поток — проверяйте InvokeRequired сначала. ConfigureAwait(False) улучшает производительность в library-коде (без захвата контекста), но предотвращает UI-доступ после. SynchronizationContext.Post — более общий способ маршалинга в конкретный контекст (UI, ASP.NET).
' Windows.Forms.Timer (UI thread, simple)
Dim timer1 As New Windows.Forms.Timer With {.Interval = 1000}
AddHandler timer1.Tick, Sub(sender, e)
Label1.Text = Date.Now.ToString()
End Sub
timer1.Start()
' System.Threading.Timer (thread pool, not UI thread)
Dim timer2 As New Threading.Timer(
Sub(state)
' runs on thread pool thread!
' must marshal to UI thread to update controls:
Me.Invoke(Sub() Label1.Text = Date.Now.ToString())
End Sub,
Nothing, 0, 1000)
' System.Timers.Timer (component, fires on thread pool)
Dim timer3 As New Timers.Timer With {.Interval = 1000, .AutoReset = True}
AddHandler timer3.Elapsed, Sub(sender, e)
Me.Invoke(Sub() Label1.Text = Date.Now.ToString())
End Sub
timer3.Start()
' UI thread marshaling
If Label1.InvokeRequired Then
Label1.Invoke(Sub() Label1.Text = "updated")
Else
Label1.Text = "updated"
End If
' async with ConfigureAwait
Await SomeAsync().ConfigureAwait(True) ' capture context (default)
Await SomeAsync().ConfigureAwait(False) ' don't capture (faster, but no UI access)
' SynchronizationContext for custom marshaling
Dim uiContext = SynchronizationContext.Current
uiContext.Post(Sub() Label1.Text = "done", Nothing)Concurrent-коллекции и Channels
Concurrent-коллекции (ConcurrentDictionary, Queue, Stack, Bag) — потокобезопасные альтернативы обычным коллекциям — используйте их вместо локов. AddOrUpdate/GetOrAdd на ConcurrentDictionary — атомарные составные операции. BlockingCollection реализует паттерн producer-consumer с блокирующими Add/Take — отлично для worker-очередей. Channels (System.Threading.Channels) — современная, async-friendly альтернатива с backpressure. Для потокобезопасности UI используйте Invoke/BeginInvoke вместо concurrent-коллекций. Всегда предпочитайте эти встроенные примитивы ручному лочению — они протестированы и оптимизированы.
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()Реестр и Windows API
Чтение и запись реестра
Windows Registry хранит настройки приложений и системную конфигурацию. Используйте Microsoft.Win32.Registry для доступа. HKEY_CURRENT_USER (HKCU) per-user (без admin); HKEY_LOCAL_MACHINE (HKLM) системно (требует admin). Всегда предоставляйте default value при чтении (возвращает Nothing, если отсутствует). Используйте RegistryValueKind для указания типа (String, DWord, Binary). Ключ Run авто-запускает приложения при входе. Оборачивайте RegistryKey в Using для закрытия дескрипторов. Будьте осторожны при редактировании реестра — ошибки могут сломать 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: вызов Windows API
P/Invoke (Platform Invoke) позволяет VB вызывать неуправляемые функции Windows API через DllImport. Объявляйте сигнатуру функции, соответствующую C API; runtime маршалит типы автоматически (String ↔ LPSTR, Integer ↔ DWORD). Используйте ByRef для выходных параметров и StructLayout для структур, передаваемых по ссылке. CharSet.Auto выбирает ANSI или Unicode по ОС. Частые библиотеки: user32 (окна, сообщения), kernel32 (система, файлы), gdi32 (графика). Всегда оборачивайте P/Invoke в класс NativeMethods. Pinvoke.net — отличный ресурс для сигнатур.
Imports System.Runtime.InteropServices
Public Class NativeMethods
' MessageBox from user32.dll
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Public Shared Function MessageBox(hWnd As IntPtr, text As String,
caption As String, type As Integer) As Integer
End Function
' GetSystemMetrics
<DllImport("user32.dll")>
Public Shared Function GetSystemMetrics(nIndex As Integer) As Integer
End Function
' Beep
<DllImport("kernel32.dll")>
Public Shared Function Beep(frequency As Integer, duration As Integer) As Boolean
End Function
' SendMessage (for custom control messages)
<DllImport("user32.dll", CharSet:=CharSet.Auto)>
Public Shared Function SendMessage(hWnd As IntPtr, msg As Integer,
wParam As IntPtr, lParam As IntPtr) As IntPtr
End Function
' structures
<StructLayout(LayoutKind.Sequential)>
Public Structure POINT
Public X As Integer
Public Y As Integer
End Structure
<DllImport("user32.dll")>
Public Shared Function GetCursorPos(ByRef lpPoint As POINT) As Boolean
End Function
End Class
' usage
NativeMethods.MessageBox(IntPtr.Zero, "Hello from API!", "Test", 0)
NativeMethods.Beep(440, 500) ' 440 Hz for 500ms
Dim p As NativeMethods.POINT
NativeMethods.GetCursorPos(p)
Console.WriteLine($"Cursor at {p.X}, {p.Y}")
' screen dimensions
Dim width = NativeMethods.GetSystemMetrics(0) ' SM_CXSCREEN
Dim height = NativeMethods.GetSystemMetrics(1) ' SM_CYSCREENProcess и Shell-операции
Process.Start запускает внешние программы. UseShellExecute=False с RedirectStandardOutput позволяет программно захватывать вывод — необходимо для CLI-инструментов. Всегда WaitForExit и читайте вывод, чтобы избежать deadlocks (буфер вывода может заполниться и заблокировать процесс). GetProcesses перечисляет запущенные процессы; GetProcessesByName находит конкретные. Verb='runas' повышает до admin (вызывает UAC). Для долгоживущих процессов подписывайтесь на событие Exited или используйте async-паттерны. Будьте осторожны с пользовательскими путями к файлам — валидируйте для предотвращения command injection.
Imports System.Diagnostics
' start a process
Process.Start("notepad.exe")
Process.Start("notepad.exe", "file.txt")
Process.Start("https://example.com") ' opens default browser
Process.Start("mailto:[email protected]")
' with ProcessStartInfo (more control)
Dim psi As New ProcessStartInfo With {
.FileName = "cmd.exe",
.Arguments = "/c dir",
.UseShellExecute = False,
.RedirectStandardOutput = True,
.CreateNoWindow = True
}
Using p As Process = Process.Start(psi)
Dim output = p.StandardOutput.ReadToEnd()
p.WaitForExit()
Console.WriteLine(output)
End Using
' async read output
Using p As Process = Process.Start(psi)
Dim output = Await p.StandardOutput.ReadToEndAsync()
Await Task.Run(Sub() p.WaitForExit())
End Using
' get running processes
For Each proc In Process.GetProcesses()
Console.WriteLine($"{proc.ProcessName} (PID {proc.Id})")
Next
' find specific process
Dim excel = Process.GetProcessesByName("EXCEL").FirstOrDefault()
If excel IsNot Nothing Then
Console.WriteLine($"Excel memory: {excel.WorkingSet64 1024} KB")
End If
' run as admin
psi.Verb = "runas" ' triggers UAC prompt
Process.Start(psi)Environment и системная информация
Environment предоставляет системную и пользовательскую информацию. GetFolderPath с SpecialFolder — правильный способ найти стандартные каталоги (AppData, MyDocuments, Temp) — никогда не хардкодьте пути. Переменные окружения настраивают поведение между машинами. GetCommandLineArgs включает исполняемый как первый элемент. Для детальной информации о железе используйте WMI (System.Management) — запросы классов вроде Win32_Processor, Win32_LogicalDisk. Screen.AllScreens (Windows Forms) даёт информацию о мониторах для multi-display-настроек. Всегда используйте эти API вместо угадывания путей или настроек.
' environment variables
Dim path = Environment.GetEnvironmentVariable("PATH")
Environment.SetEnvironmentVariable("MY_VAR", "value")
Dim allVars = Environment.GetEnvironmentVariables()
' special folders
Dim appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)
Dim myDocs = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
Dim temp = Environment.GetFolderPath(Environment.SpecialFolder.Temp)
Dim programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)
' system info
Console.WriteLine($"Machine: {Environment.MachineName}")
Console.WriteLine($"User: {Environment.UserName}")
Console.WriteLine($"OS: {Environment.OSVersion}")
Console.WriteLine($"64-bit: {Environment.Is64BitOperatingSystem}")
Console.WriteLine($"Processors: {Environment.ProcessorCount}")
Console.WriteLine($".NET: {Environment.Version}")
Console.WriteLine($"Uptime: {Environment.TickCount64 1000} seconds")
' command line args
Dim args = Environment.GetCommandLineArgs()
' args(0) is the executable path
' current directory
Environment.CurrentDirectory = "C: emp"
' system power
Console.WriteLine($"System uptime: {TimeSpan.FromMilliseconds(Environment.TickCount64)}")
' WMI for detailed hardware info
Imports System.Management
Dim searcher As New ManagementObjectSearcher("SELECT * FROM Win32_Processor")
For Each obj In searcher.Get()
Console.WriteLine($"CPU: {obj("Name")}")
Next
' screen info
Dim screens = Screen.AllScreens
For Each s In screens
Console.WriteLine($"{s.DeviceName}: {s.Bounds.Width}x{s.Bounds.Height}")
NextINI-файлы и конфигурация
INI-файлы устарели, но всё ещё используются для простых конфигураций. Функции Windows API (WritePrivateProfileString, GetPrivateProfileString) обрабатывают парсинг. Для современного .NET используйте app.config (XML) или appsettings.json (JSON) с ConfigurationManager или Microsoft.Extensions.Configuration. JSON-конфиг поддерживает вложенные структуры, массивы и environment-специфичные переопределения (appsettings.Production.json). Configuration builders могут комбинировать несколько источников (файлы, переменные окружения, командная строка). Всегда отделяйте конфигурацию от кода для гибкости развёртывания.
' 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 }Коллекции и дженерики углублённо
List, Dictionary и HashSet
List(Of T) — рабочая лошадка — динамический массив с O(1) append, O(n) insert/remove. Dictionary(Of K,V) предоставляет O(1) поиск по ключу (хеш-таблица). HashSet(Of T) хранит уникальные элементы с быстрыми set-операциями (Union, Intersect, Except). SortedDictionary держит ключи отсортированными (binary search tree). LinkedList — двусвязный (быстрый insert/remove где угодно, но нет индексированного доступа). Выбирайте по паттернам доступа: List для индексированного, Dictionary для ключевого, HashSet для принадлежности, SortedDictionary для упорядоченной итерации. Все generic (типобезопасные, без boxing).
Imports System.Collections.Generic
' List(Of T)
Dim list As New List(Of String) From {"a", "b", "c"}
list.Add("d")
list.AddRange({"e", "f"})
list.Insert(0, "first")
list.Remove("b")
list.RemoveAt(0)
list.Sort()
list.Reverse()
Dim count = list.Count
Dim hasA = list.Contains("a")
Dim idx = list.IndexOf("c")
Dim slice = list.GetRange(0, 2) ' first 2 elements
Dim arr = list.ToArray()
' Dictionary(Of TKey, TValue)
Dim dict As New Dictionary(Of String, Integer)
dict.Add("one", 1)
dict("two") = 2
dict.TryGetValue("one", Dim val As Integer) ' val = 1, returns True
For Each kvp In dict
Console.WriteLine($"{kvp.Key} = {kvp.Value}")
Next
Dim keys = dict.Keys.ToList()
Dim vals = dict.Values.ToList()
' HashSet(Of T) (unique elements, O(1) lookup)
Dim set1 As New HashSet(Of Integer) From {1, 2, 3}
Dim set2 As New HashSet(Of Integer) From {3, 4, 5}
set1.Add(3) ' no effect (already exists)
Dim union = New HashSet(Of Integer)(set1) : union.UnionWith(set2) ' 1,2,3,4,5
Dim inter = New HashSet(Of Integer)(set1) : inter.IntersectWith(set2) ' 3
Dim diff = New HashSet(Of Integer)(set1) : diff.ExceptWith(set2) ' 1,2
' SortedDictionary / SortedList (sorted by key)
Dim sorted As New SortedDictionary(Of String, Integer)
sorted("zebra") = 1
sorted("apple") = 2 ' iterates in sorted order
' LinkedList (fast insert/remove at ends)
Dim ll As New LinkedList(Of Integer)
ll.AddFirst(1)
ll.AddLast(2)Queue, Stack и ObservableCollection
Queue (FIFO) и Stack (LIFO) специализированы для упорядоченных паттернов доступа. ObservableCollection вызывает события CollectionChanged при добавлении/удалении/замене элементов — необходима для MVVM data binding. BindingList расширяет это возможностями редактирования (AddingNew, AllowEdit, AllowRemove). ReadOnlyCollection оборачивает список для предотвращения модификации (возвращает оригинал, не копию). KeyedCollection комбинирует семантику списка и словаря (доступ по индексу или ключу). Для потокобезопасных версий используйте ConcurrentQueue, ConcurrentStack, ConcurrentDictionary из System.Collections.Concurrent.
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
' Queue(Of T) (FIFO)
Dim queue As New Queue(Of String)
queue.Enqueue("first")
queue.Enqueue("second")
Dim next1 = queue.Dequeue() ' "first"
Dim peek = queue.Peek() ' "second" (doesn't remove)
Dim hasItems = queue.Count > 0
' Stack(Of T) (LIFO)
Dim stack As New Stack(Of Integer)
stack.Push(1)
stack.Push(2)
stack.Push(3)
Dim top = stack.Pop() ' 3
Dim peek = stack.Peek() ' 2
' ObservableCollection (notifies on changes - for data binding)
Dim people As New ObservableCollection(Of Person)()
AddHandler people.CollectionChanged, Sub(sender, e)
If e.Action = NotifyCollectionChangedAction.Add Then
Console.WriteLine($"Added: {CType(e.NewItems(0), Person).Name}")
End If
End Sub
people.Add(New Person With {.Name = "Alice"}) ' triggers event
' BindingList(Of T) (editable, for DataGridView)
Dim list As New BindingList(Of Person)()
list.AddingNew = Function() New Person()
list.RaiseListChangedEvents = True
' ReadOnlyCollection (wrapper, prevents modification)
Dim ro As New ReadOnlyCollection(Of String)(list)
' ro.Add("x") ' throws NotSupportedException
' KeyedCollection (abstract, dictionary-like by key)
Public Class PersonCollection
Inherits KeyedCollection(Of String, Person)
Protected Overrides Function GetKeyForItem(item As Person) As String
Return item.Name
End Function
End ClassGeneric-методы и ограничения
Дженерики предоставляют типобезопасность без накладных расходов boxing/unboxing. Ограничения (Of T As ...) ограничивают параметры типа: Class (ссылочные типы), Structure (value-типы), New (конструктор без параметров), IComparable (интерфейс) или базовый класс. Несколько ограничений используют фигурные скобки: Of T As {Class, New, IComparable(Of T)}. Generic-методы выводят тип из аргументов. Дженерики reified в .NET (информация о типе доступна во время выполнения, в отличие от type erasure в Java). Используйте дженерики для коллекций, алгоритмов и utility-классов, чтобы избежать дублирования кода, сохраняя типобезопасность.
' 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 ClassКастомные коллекции и IEnumerable
Реализация IEnumerable(Of T) включает итерацию For Each. Шаблон Iterator/Yield (VB 14+) генерирует конечные автоматы для отложенного вычисления — значения создаются по требованию. Это эффективно использует память для больших или бесконечных последовательностей. CollectionBase — это устаревший базовый класс; для нового кода предпочтительнее реализовать ICollection(Of T) или унаследовать от List(Of T). Пользовательские коллекции имеют смысл, когда нужно особое поведение (проверка, уведомления, отложенная загрузка). В большинстве случаев объединяйте существующие коллекции, а не создавайте их с нуля.
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 ClassКортежи и ValueTuple
ValueTuple (VB 15+) — современный способ возвращать несколько значений — легковесный (тип-значение), с именованными полями для читаемости. Tuple (System.Tuple) — ссылочный тип только с именами Item1/Item2. ValueTuple поддерживает деконструкцию (Dim (a, b) = ...) и поэлементное равенство. Используйте ValueTuple для внутренних возвратов из методов и промежуточных результатов LINQ. Для публичных API предпочитайте записи или классы со значимыми именами. Шаблон пустышки (_) игнорирует нежелательные значения. ValueTuple особенно полезен в LINQ для анонимных проекций, которые нужно вернуть из методов.
' 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 middleОтладка и диагностика
Debug и Trace
Класс Debug компилируется в сборках Release (использует ConditionalAttribute); Trace работает во всех сборках. TraceListeners направляют вывод в файлы, консоль или журнал событий. TraceSwitch (настраиваемый через app.config) управляет уровнем детализации во время выполнения без перекомпиляции. TraceSource обеспечивает именованную гранулярную трассировку с типами событий (Error, Warning, Info, Verbose). Всегда используйте Trace для производственной диагностики и Debug для утверждений разработки. Атрибут Conditional полностью удаляет вызовы методов в сборках Release — нулевые накладные расходы. Настраивайте слушатели в app.config для гибкости развёртывания.
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.AllОтладчик и точки останова
Атрибуты отладчика настраивают отображение объектов в отладчике. DebuggerDisplay управляет строкой сводки; DebuggerBrowsable управляет видимостью членов (RootHidden разворачивает коллекции). DebuggerStepThrough/Hidden управляют поведением при пошаговом выполнении. Условные точки останова приостанавливают выполнение только когда условие истинно — необходимы для поиска ошибок в больших циклах. Точки трассировки записывают сообщения без изменения кода. Окно Immediate вычисляет выражения во время выполнения. Edit and Continue позволяет исправлять код без перезапуска. Освойте эти функции отладчика, чтобы значительно ускорить отладку.
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 = newValueСтратегии обработки исключений
Перехватывайте специфичные исключения раньше общих (сначала самые специфичные). Используйте Throw (не Throw ex), чтобы сохранить исходный стек вызовов. Фильтры исключений (предложение When) добавляют условия без перехвата — исключение распространяется дальше, если фильтр ложен. Пользовательские исключения должны наследоваться от Exception (не от ApplicationException, который устарел), быть Serializable и реализовывать три конструктора. Оборачивайте низкоуровневые исключения в доменно-специфичные, чтобы абстрагировать детали реализации. Никогда не перехватывайте и не подавляйте исключения молча — как минимум регистрируйте их в журнале. Используйте глобальные обработчики исключений (AppDomain.UnhandledException, Application.ThreadException) как страховку.
' 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 ClassВедение журналов и Журнал событий
Windows Event Log идеально подходит для системных событий (запуск/остановка службы, критические ошибки) — требуются права администратора для создания источника. Для журналирования приложений используйте структурированную платформу журналирования: Serilog (современная, дружественная к JSON), NLog или log4net. Структурированное журналирование (logger.Information("User {UserId}...", id)) сохраняет типы данных для запросов, в отличие от конкатенации строк. Уровни журнала (Debug, Info, Warning, Error, Fatal) позволяют фильтровать во время выполнения. Всегда включайте контекст (ID пользователя, ID заказа) в записи журнала для отладки. Счётчики производительности отслеживают CPU, память и пользовательские метрики в продакшене.
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)Модульное тестирование и TDD
Модульные тесты проверяют отдельные методы изолированно. MSTest (встроенный), xUnit и NUnit — основные фреймворки. TestInitialize/TestCleanup выполняются до/после каждого теста. Методы Assert проверяют результаты (AreEqual, IsTrue, ThrowsException). DataTestMethod/DataRow параметризуют тесты с несколькими входными данными. ExpectedException (или Try/Catch/Assert.Fail) проверяет случаи исключений. Используйте Moq для создания заглушек зависимостей (интерфейсов) — настройте ожидаемое поведение и проверяйте вызовы. TDD (разработка через тестирование) сначала пишет тесты, затем код. Стремитесь к высокому покрытию бизнес-логики; пропускайте тривиальные геттеры/сеттеры свойств. Запускайте тесты в CI/CD для выявления регрессий.
' 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)Коллекции и словари
ArrayList и List(Of T)
List(Of T) — современная, типобезопасная замена ArrayList. Всегда предпочитайте его — вы получаете проверку типов во время компиляции и лучшую производительность (нет упаковки для типов-значений). Предложение From инициализирует список встроенным образом. FindAll/Find используют предикаты (лямбды). AddRange добавляет несколько элементов сразу.
' 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) — стандартная хеш-таблица. Используйте TryGetValue для безопасного получения значения (возвращает False, если отсутствует, без исключения). Add выбрасывает исключение, если ключ существует; индексатор (dict(key)) молча перезаписывает. Коллекции Keys/Values позволяют перебирать или извлекать. LINQ работает со словарями — они 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 и SortedSet
HashSet предназначен для быстрой проверки принадлежности и операций над множествами (объединение, пересечение, разность). SortedSet хранит элементы в отсортированном порядке (двоичное дерево поиска). Оба имеют операции O(1) или O(log n) — намного быстрее, чем List.Contains (O(n)). Используйте HashSet, когда порядок не важен; SortedSet, когда нужна упорядоченная итерация.
' 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 и Stack
Queue — это FIFO (первым пришёл — первым ушёл) — используйте для планирования задач, BFS. Stack — LIFO (последним пришёл — первым ушёл) — используйте для отмены, вычисления выражений, DFS. Оба имеют O(1) для enqueue/dequeue и push/pop. Создайте Stack из массива, чтобы обратить его одной строкой. Peek просматривает следующий элемент без удаления.
' 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}Параллельные коллекции
Параллельные коллекции потокобезопасны — используйте их вместо блокировки обычных коллекций. AddOrUpdate и GetOrAdd у ConcurrentDictionary атомарны. ConcurrentQueue/Stack без блокировок. ConcurrentBag работает быстрее всего, когда каждый поток в основном производит и потребляет свои собственные элементы. BlockingCollection — стандартный шаблон для очередей типа производитель-потребитель.
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)Файловый ввод-вывод продвинутый
Чтение и запись потоков
Всегда оборачивайте потоки в блоки Using для гарантированного освобождения (закрывает файл даже при исключениях). File.ReadAllText/ReadAllLines удобны для небольших файлов. Для больших файлов используйте StreamReader построчно. Асинхронный ввод-вывод (ReadToEndAsync) поддерживает отзывчивость UI — никогда не вызывайте синхронный файловый ввод-вывод в потоке 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 UsingБинарные данные и CSV
BinaryWriter/Reader обрабатывают типизированные бинарные данные — ReadInt32/ReadDouble должны точно совпадать с порядком записи. TextFieldParser (в Microsoft.VisualBasic.FileIO) — надёжный способ разбора CSV — обрабатывает поля в кавычках со встроенными запятыми и переносами строк. Не создавайте собственный CSV-парсер с Split(","). Для сложных CSV рассмотрите пакет CsvHelper из NuGet.
Imports System.IO
Imports Microsoft.VisualBasic.FileIO
' BinaryWriter/Reader
Using fs As New FileStream("data.bin", FileMode.Create),
bw As New BinaryWriter(fs)
bw.Write(42I) ' Integer
bw.Write(3.14R) ' Double
bw.Write("hello") ' String (length-prefixed)
End Using
Using fs As New FileStream("data.bin", FileMode.Open),
br As New BinaryReader(fs)
Dim n = br.ReadInt32()
Dim d = br.ReadDouble()
Dim s = br.ReadString()
End Using
' CSV with TextFieldParser (handles quoted fields)
Using parser As New TextFieldParser("data.csv")
parser.TextFieldType = FieldType.Delimited
parser.SetDelimiters(",")
parser.HasFieldsEnclosedInQuotes = True
' skip header
If Not parser.EndOfData Then parser.ReadFields()
While Not parser.EndOfData
Dim fields = parser.ReadFields()
Console.WriteLine($"Name: {fields(0)}, Age: {fields(1)}")
End While
End Using
' write CSV
Dim lines = {"name,age", "Alice,30", "Bob,25"}
File.WriteAllLines("out.csv", lines)Операции с путями
Всегда используйте Path.Combine (не конкатенацию строк) для объединения путей — он правильно обрабатывает разделители. Path.GetInvalidFileNameChars помогает очистить пользовательский ввод для имён файлов. FileInfo/DirectoryInfo кэшируют метаданные файла — используйте их, когда нужно несколько свойств. SearchOption.AllDirectories рекурсивен; для огромных деревьев используйте EnumerateFiles (потоковая обработка).
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())Наблюдение за файлами
FileSystemWatcher отслеживает изменения в каталоге. Установите NotifyFilter, чтобы ограничить, какие изменения вызывают события (производительность). Главная ловушка: редакторы часто вызывают несколько событий Changed при каждом сохранении (запись, сброс, метаданные) — устраняйте дребезг, отслеживая время последнего события. Ошибки (например, переполнение буфера из-за слишком большого количества событий) также требуют обработки.
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 SubСериализация
System.Text.Json — современный высокопроизводительный сериализатор JSON (заменяет Newtonsoft.Json для нового кода). XmlSerializer обрабатывает XML — обратите внимание, что требует конструктор без параметров и публичные свойства. Для коллекций сериализуйте List(Of T) напрямую. JsonSerializerOptions с WriteIndented создаёт читаемый человеком вывод.
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)Обработка ошибок углублённо
Шаблоны Try/Catch
Перехватывайте специфичные исключения раньше общих (порядок важен). Используйте Finally для очистки — выполняется, даже если вы возвращаете значение или выбрасываете исключение внутри Try. Фильтры When (VB 14+) позволяют перехватывать на основе свойств исключения. При оборачивании исключений передавайте оригинал как innerException, чтобы сохранить стек вызовов. Избегайте перехвата Exception, если только не выбрасываете повторно.
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 TryПользовательские исключения
Пользовательские исключения должны наследоваться от Exception (или доменно-специфичного базового класса) и предоставлять несколько конструкторов: только сообщение, сообщение + внутреннее, и доменно-специфичный. Пометьте их <Serializable>, если они могут пересекать границы AppDomain. Добавьте свойства для контекста, который нужен вызывающим сторонам. Переопределите ToString для ясного журналирования.
' 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 TryСтратегии обработки исключений
Никогда не подавляйте исключения молча — как минимум регистрируйте их в журнале. Используйте Using для ресурсов IDisposable (чище, чем try/finally). Регистрируйте глобальные обработчики для AppDomain.UnhandledException (последний шанс) и Application.ThreadException (поток UI WinForms). AggregateException оборачивает несколько исключений из параллельного кода — выровняйте и обработайте каждое 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 TryШаблоны Result (функциональные)
Шаблон Result возвращает успех/неудачу как значение, а не выбрасывая исключение — полезно для ожидаемых неудач (разбор, проверка), где исключения были бы дорогими и шумными. Комбинируйте с методами расширения (OnSuccess, OnFailure) для цепочек. Используйте исключения для действительно исключительных случаев; Result — для предсказуемых неудач.
' 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)Интеграция журналирования
Используйте платформу журналирования (NLog, Serilog, log4net) — не Console.WriteLine. Уровни журнала (Trace/Debug/Info/Warn/Error/Fatal) позволяют фильтровать во время выполнения через конфигурацию. Структурированное журналирование (Serilog) сохраняет имена полей — лучше для поиска в таких инструментах, как ELK или Seq. Всегда включайте контекст (ID пользователей, имена операций) в сообщения журнала.
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 TryБаза данных ADO.NET
Подключения и команды
Всегда используйте параметры (никогда конкатенацию строк) для предотвращения SQL-инъекций. Блоки Using гарантируют закрытие подключений даже при исключениях. ExecuteReader для SELECT; ExecuteNonQuery для INSERT/UPDATE/DELETE; ExecuteScalar для одиночного значения (например, COUNT или SCOPE_IDENTITY). Пул подключений автоматический — открывайте поздно, закрывайте рано.
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 UsingТранзакции
Транзакции обеспечивают атомарность — все операции либо выполняются, либо все терпят неудачу. BeginTransaction для одного подключения — для одной базы данных. TransactionScope обрабатывает распределённые транзакции между несколькими подключениями (и даже несколькими базами данных) — вызовите Complete для фиксации. Выбирайте уровень изоляции тщательно: Serializable самый безопасный, но медленный; ReadCommitted — по умолчанию.