Skip to content

Visual Basic チートシート

Windowsアプリ向けのシンプルなイベント駆動言語。

01

変数、型と演算子

変数宣言と組み込み型

VB.NETはDim宣言で静的型付けされます。一般的な型:String、Integer(32ビット)、Long(64ビット)、Double(64ビット浮動小数点)、Decimal(金融用高精度)、Boolean、Date、Char。型サフィックス(DecimalはD、SingleはF、LongはL)でリテラル型を強制。Option Explicit Onが変数宣言を強制(タイプミスを防止)。Option Infer Onが明示的型なしのDimで型推論を有効化。Null許容型(Integer?)が値型をラップしNothingを保持可能 — データベースとAPIに便利。

vb
' 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

定数、列挙型と構造体

Constがコンパイル時定数を宣言(暗黙的にShared、変更不可)。Enumが名前付き整数定数を定義 — [Enum].Parseで文字列を変換、CIntで数値を取得。ブラケット表記[Error]は予約語をエスケープ。<Flags>属性がenumをビットフィールドとしてマークし、Or/And演算子で組み合わせを許可 — 権限とオプションに一般的。フラグにはビット幅を制御するため基になる型(As Integer)を常に指定。

vb
' 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は汎用版)。DirectCastは最も厳密 — ランタイム型が完全に一致する場合のみ動作。TryCastは失敗時に例外をスローせずNothingを返す(参照型のみ)。ユーザー入力には例外を避けるためParseよりTryParseを推奨。IsNumeric/IsDateは便利な検証ヘルパー。CIntは丸め(銀行家丸め)、Int()は切り捨て。

vb
' 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参照回避に推奨)。&が文字列連結演算子(数値加算もできる+ではない)。デフォルトでAndAlso/OrElseを使用して短絡し、Nothingオブジェクトのチェックなどのエラーを回避。

vb
' 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(16進)などのフォーマット指定子をサポート。String.Formatは位置プレースホルダ{0}、{1}とオプション配置({0,-10} = 左揃え、幅10)を使用。大きな文字列を構築するループにはStringBuilder(&ではなく)を使用し多くの中間文字列の作成を回避。Integer.Parseは無効入力でスロー;安全のためTryParseを使用。

vb
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
02

制御フロー

If...Then...Elseと三項演算子

If...Then...ElseIf...ElseがVBの条件分岐。AndAlso/OrElseは短絡(不要なら右辺を評価しない) — 条件にはAnd/Orより常にこれらを推奨。If()関数(VB 14+)は三項演算子:If(condition, trueVal, falseVal)。2引数のIf()はnull合体演算子:If(maybeNull, defaultValue)。古いIIf()関数は避ける — 常に両分岐を評価しObjectを返す(ボクシング)。単一行IfはEnd If不要。

vb
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はVBのswitch文 — 連鎖If...ElseIfよりクリーン。Caseは複数値(カンマ区切り)、範囲(To)、比較演算子(Is >= 90)をサポート。Case Elseがデフォルト分岐。C#のswitchと異なりVBはフォールスルーしない — 各Caseは独立して実行されBreak不要。Select Caseは文字列、数値、オブジェクト(Is使用)で動作。離散値でのディスパッチに最適。

vb
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 Select

For、For Eachとイテレータ

For...To...NextはオプションのStep(カウントダウンは負)で範囲を反復。For Eachは任意のIEnumerableを反復 — コレクションにクリーン。Exit Forがループを脱出;Continue Forが次の反復へスキップ。イテレータ関数(Yield付き)はシーケンスを遅延生成 — 値はオンデマンドで生成され、大規模や無限シーケンスにメモリ効率的。Yieldは1値を返し、次の反復で中断した場所から再開。これはC#のyield returnのVB版。

vb
' 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
Next

Do While、Do UntilとWhile

Do Whileは条件がTrueの間実行;Do Untilは条件がFalseの間実行(Trueになるまで)。条件を最後に置く(Loop While/Until)と本体が少なくとも1回実行されることを保証 — メニューと入力検証に便利。While...End Whileは古くシンプルな形式。Exit Doが脱出;Continue Doが次の反復へスキップ。ループに入らない可能性がある場合はDo Whileを、少なくとも1回実行する必要がある場合はDo...Loop Whileを選択。

vb
' 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
Loop

With...End WithとGoTo

With...End Withは1つのオブジェクトの複数メンバーに変数名を繰り返さずにアクセスする構文糖 — 初期化とビルダーに便利。GoToはラベルにジャンプ;現代VBでは深くネストされたループの脱出やレガシーOn Error GoToエラー処理以外では推奨されない。On Error GoToはVB6スタイルのエラー処理(Resume Nextがエラー行をスキップ) — 新規コードでは構造化Try/Catchを推奨。Errオブジェクトが最後のエラーの説明と番号を保持。

vb
' 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
03

プロシージャ、関数とイベント

SubとFunctionの基礎

Subはアクションを実行(戻り値なし);Functionは値を返す。Returnが値で関数を終了。古いスタイルの'関数名への代入'(Multiply = a * b)はまだ動作するがReturnの方が明確。Sub呼び出し時、括弧はオプション(Greet "Alice"またはGreet("Alice"))。戻り値のために呼び出す関数には括弧が必要。デフォルトでVBは戻り値を代入せずに関数を呼べるが、混乱を招く可能性 — 明示的に。

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は参照を渡す — 変更は呼び出し元に影響する(C#のrefのように)。プロシージャが呼び出し元の変数を変更する必要がある、または複数値を返す場合にByRefを使用。Optionalパラメータはデフォルトを持ち、必須パラメータの後に来る必要。ParamArrayは可変個の引数を受け入れる(C#のparamsのように)最後のパラメータである必要。名前付き引数(name:=value)は可読性を向上しオプションパラメータのスキップを許可。

vb
' 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)

ラムダ式とデリゲート

ラムダはインライン関数:値を返すものはFunction(...)、返さないものはSub(...)。Func(Of T, TResult)は関数用の組み込みデリゲート型;Action(Of T)はSub用(戻り値なし)。AddressOfが名前付きメソッドからデリゲートを作成。ラムダはLINQに不可欠(Where、Select、OrderByは関数引数を取る)。複数行ラムダはFunction...End Function(またはSub...End Sub)を使用。デリゲートは型安全な関数ポインタ — コールバック、イベント、ストラテジーパターンに便利。

vb
' 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(ランタイム、動的)で処理。WithEvents + Handlesは宣言的だが変数はモジュール/クラスレベルである必要。AddHandler/RemoveHandlerはランタイムでサブスクライブ/アンサブスクライブを許可 — 動的ワイヤリングに便利。イベントハンドラはイベントのシグネチャに一致する必要。イベントはWindows FormsとWPFの基幹(ボタンクリック、フォームロードなど)。

vb
' 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キーワードが必要。再帰は関数が自身を呼び出す — 終了するためにベースケースが必要。深い再帰でスタックオーバーフローに注意。Shared(C#のstatic)メンバーはインスタンスではなく型に属する — オブジェクトを作成せずClassName.Memberで呼び出し。Sharedコンストラクタは型ごとに1回実行、静的データの初期化に最適。

vb
' 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
04

文字列とテキスト処理

文字列の基礎とイミュータビリティ

VB.NETの文字列はイミュータブル — すべてのメソッド(ToUpper、Replaceなど)は新しい文字列を返す;元は変更されない。等価比較には=を使用(VB固有)。IndexOfは部分文字列の最初のインデックスを返す(見つからない場合は-1)。Splitは文字列を配列に分割;Joinは配列を文字列に結合。Trimは両端から空白(または指定文字)を削除。大文字小文字を区別しない比較にはStringComparison.OrdinalIgnoreCaseを使用。ループ内での重い文字列操作にはStringBuilderを使用。

vb
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)) ' hello

StringBuilderとパフォーマンス

Stringはイミュータブル — すべての&または+=が新しい文字列を作成し、以前のすべての内容をコピー。ループ内ではO(n²)。StringBuilderはミュータブル — Append/Insert/Removeが同じバッファを修正しO(n)に。大きな文字列を構築するループには常にStringBuilderを使用。StringBuilderはチェーン可能(Appendが同じインスタンスを返す)。おおよそのサイズが分かっていれば初期Capacityを設定し再割り当てを回避。ループ外の一度きりの連結には&で十分でより読みやすい。

vb
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桁区切り、:X16進、:D5ゼロ埋め、:Pパーセント)。日付フォーマット:yyyy-MM-dd、HH:mm:ss、dddd(完全な曜日名)。位置プレースホルダやフォーマット文字列を動的に構築する場合にString.Formatを使用。カルチャ対応アプリ(国際化)では、小数区切り、通貨記号、日付フォーマットを制御するためCultureInfoをToString/Formatに渡す。

vb
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(空白)、+(1回以上)、*(0回以上)、{n}(ちょうどn回)、^/$(開始/終了)。メール、電話などのユーザー入力を常に正規表現で検証。

vb
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バイト)。charリテラルにはcサフィックスを使用("A"c)。Charメソッド(IsDigit、IsLetter、IsUpper)は検証に便利。Asc/Chrが文字とASCIIコードを相互変換。ToCharArrayは文字列をミュータブルなchar配列に変換(文字列自体はイミュータブル)。Encoding.UTF8.GetBytesは文字列をバイト配列に変換(ファイルI/Oとネットワークに不可欠)。Base64(Convert.ToBase64String)はバイナリデータをテキストとしてエンコード — データURI、メール添付、APIで使用。

vb
' 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))
05

配列とコレクション

配列:宣言、インデックスとメソッド

VBの配列は0インデックスで固定サイズ。Dim arr(n)はn+1要素を作成(0からn)。配列リテラルは{ }を使用。多次元配列(,)は矩形;ジャグ配列(()())は配列の配列(各行が異なる長字を持てる)。ReDimは配列をリサイズ — Preserveは既存値を保持(なしの場合、値はクリア)。Array.Sort/Reverse/Clear/IndexOf/Copyは静的ヘルパー。サイズが頻繁に変わる場合は配列の代わりにList(Of T)を使用。

vb
' 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)
Next

List(Of T):動的配列

List(Of T)は定番の動的コレクション — Addすると自動的に成長。Countが要素数(容量ではない)。Add/Insert/Remove/RemoveAt/Contains/IndexOfがコアメソッド。Find/FindAll/Existsはカスタム検索の述語(ラムダ)を取る。Sortはカスタム順序付けのComparisonデリゲートを取れる。List(Of T)は内部で配列をラップし、満杯になるとリサイズ(償却O(1) Add)。固定サイズコレクションが必要な場合はToArray()で配列に変換。

vb
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 age

Dictionary、HashSetとQueue/Stack

Dictionary(Of TKey, TValue)はキーを値にマッピング — キーでO(1)ルックアップ。例外と二重ルックアップ(ContainsKey + インデクサ)を避けるためTryGetValueを使用。KeyValuePairで反復。HashSet(Of T)はO(1)追加/含有でユニーク要素を格納 — 重複除去と集合操作(UnionWith、IntersectWith、ExceptWith)に使用。QueueはFIFO(Enqueue/Dequeue);StackはLIFO(Push/Pop)。これらのコレクションはすべてジェネリック(型安全、ボクシングなし)。アクセスパターンに基づいて選択:ルックアップ→Dictionary、ユニーク性→HashSet、順序→Queue/Stack。

vb
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())   ' 1

LINQ:クエリとメソッド構文

LINQ(Language Integrated Query)はコレクションを宣言的に変換。クエリ構文(From...Where...Select)はSQL風で複雑クエリに読みやすい。メソッド構文(.Where().Select())は流暢でチェーンしやすい。両者は同じコードにコンパイル。主要演算子:Where(フィルタ)、Select(変換)、OrderBy(ソート)、GroupBy(グループ化)、Take/Skip(ページネーション)、First/FirstOrDefault(検索)、Any/All(テスト)、Sum/Average/Max/Min(集約)。FirstOrDefaultはマッチがない場合デフォルト(0、Nothing)を返す — 例外を回避。LINQは任意のIEnumerable(配列、リスト、辞書)で動作。

vb
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 { })は1文でコレクションを作成して追加。タプル(VB 2017+)は複数値をグループ化 — 名前付きフィールド(X、Y)はItem1、Item2より明確。タプルはクラスを定義せずに複数値を返すのに最適。分解(Dim (a, b) = tuple)はタプルを変数に分割。ToDictionaryはリストを辞書に変換(キーセレクタ + 値セレクタ)。タプルは値型(構造体)なので、小さく一時的なグループ化に効率的。

vb
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
06

オブジェクト指向プログラミング

クラス、フィールドとプロパティ

クラスはデータ(フィールド/プロパティ)と振る舞い(メソッド)をバンドル。自動実装プロパティ(Property X As Type)は隠しバッキングフィールドを自動生成 — 単純データに簡潔。完全プロパティ(Get/Setブロック)は検証、計算、副作用を許可。ReadOnlyプロパティはGetのみ。Meは現在のインスタンスを参照(C#のthisのように)。コンストラクタ(Sub New)がオブジェクトを初期化。プロパティは呼び出し元にはフィールドのように見える(p.Name)がコードを実行 — このカプセル化がOOPの中核的利益。

vb
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 an Animal)。MyBase.New()が基本コンストラクタを呼び出し(最初のステートメントである必要)。Overridable(基本)+ Overrides(派生)がポリモーフィズムを有効化 — 基本参照経由でアクセスしても派生メソッドが実行。Shadows(またはOverloads)はオーバーライドではなく基本メンバーを隠す(ポリモーフィズムなし)。真のポリモーフィズムにはOverridable/Overridesを使用;変更できないメンバーを隠す必要がある場合のみShadows。VBは単一継承(1つの基本クラス)だが複数インターフェースをサポート。

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固有構文)。インターフェースはポリモーフィズムを有効化:CircleかSquareか知らなくても任意のIDrawableで動作。TypeOf x Is Tがランタイム型をチェック;DirectCastがキャスト(無効ならスロー)。コードを疎結合にするためインターフェースを使用:CircleではなくIDrawableに依存。これが依存性逆転の原則。

vb
' 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)で型安全で再利用可能なコードを記述 — ボクシングなし、キャストなし、コンパイル時型チェック。List(Of T)、Dictionary(Of K,V)、Stack(Of T)がジェネリックコレクション。制約が型パラメータを制限:Class(参照型)、Structure(値型)、New(パラメータなしコンストラクタ — New T()を許可)、特定の基本クラスやインターフェース。ジェネリクスはボクシング(値型)のパフォーマンスヒットとObjectキャストの脆弱性を回避。非ジェネリック(ArrayList)より常にジェネリックコレクション(List(Of T))を推奨。

vb
' 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 interface

部分クラスと名前空間

部分クラスはクラスを複数ファイルに分割 — コンパイラがマージ。生成コード(デザイナファイル)と手書きコードの分離、大きなクラスの分割に便利。名前空間は型を整理し名前衝突を防止(MyApp.Models.User vs MyApp.Services.User)。Importsが名前空間名をスコープに持ち込み(完全修飾不要)。RootNamespace(プロジェクト設定)がすべての型をラップ — 会社/製品名に設定。規約:名前空間はフォルダ構造に一致;論理エリアごとに1名前空間。

vb
' 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
07

エラー処理と例外

Try...Catch...Finally

Try/Catch/Finallyが構造化例外処理。Catchブロックは順序付きでチェック — 特定例外を最初に、Exception(基本)を最後に。Finallyは常に実行(Returnや未キャッチ例外でも) — クリーンアップ(ファイルクローズ、リソース解放)に使用。Throw(裸)が現在の例外を再スローしスタックトレースを保持。Whenフィルタが条件付きでキャッチ(Catch...When condition)。例外プロパティ:Message(説明)、StackTrace(呼び出しチェーン)、Source(アセンブリ)。ログなしにExceptionをサイレントキャッチしない — バグを隠す。

vb
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)に使用。

vb
' 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 Try

UsingステートメントとIDisposable

Usingがブロック終了時にDispose()が呼ばれることを保証 — すぐにガベージコレクションされないリソース(ファイル、データベース接続、ネットワークストリーム)に重要。複数リソースを1つのUsingで宣言可能(カンマ区切り)。クラスがアンマネージリソースや他のIDisposableオブジェクトを保持する場合IDisposableを実装。Disposeパターン:Dispose(disposing As Boolean)がマネージ(disposing=True時)とアンマネージリソースを解放;GC.SuppressFinalizeがファイナライザの実行を防止。リソースリークを防ぐためIDisposableオブジェクトは常にUsingでラップ。

vb
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)ユーザー入力にはParseの代わりにTryParseを使用(例外は高コストで真に例外的なケース用)。(4)具体的な例外型をスロー(ExceptionではなくArgumentOutOfRangeException)。(5)Whenフィルタでキャッチせずにログ(キャッチしないためFalseを返す)。(6)Throw(裸)で再スローしスタックトレースを保持 — Throw exはリセット。例外は通常の制御フローではなく例外的条件用。

vb
' 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は両ビルドで動作 — 本番ログ用。#If DEBUG...#End Ifが条件付きコンパイルを有効化。Debugger.Break()がプログラム的ブレークポイントのように動作。Stopwatchが経過時間を正確に測定(ベンチマーク用)。StackTraceが呼び出しチェーンをキャプチャ(ログ用)。EventLogがWindowsイベントログに書き込み(ソース作成に管理者権限が必要)。本番動作を変更せずに問題を診断するためにこれらのツールを使用。

vb
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}")
08

ファイルI/Oとストリーム

テキストファイル読み書き

File.WriteAllText/ReadAllTextが小ファイルに最も簡単。File.ReadAllLinesはString配列を返す(ファイル全体をメモリにロード)。大ファイルにはStreamReader/StreamWriterをUsingで使用 — 行ごとに読み書きしメモリを低く保つ。Usingが例外時でもストリームがクローズされることを保証。AppendAllText/AppendAllLinesが既存ファイルに追加。読み取り前にFile.Existsをチェックしフレンドリーエラーを与える。非同期I/O(ノンブロッキングUI)にはAwait付きのReadAllTextAsync/WriteAllTextAsyncを使用。

vb
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でフォーマット(インデント、camelCase)。JSONは設定ファイル、API、データ交換に最適。BinaryFormatterは避ける(セキュリティリスク — .NET 5+で削除)。

vb
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は検索パターン(*.csv)と再帰用のSearchOption.AllDirectoriesをサポート。Pathクラスがクロスプラットフォームでパスを安全に処理 — パスを結合するには(&ではなく)常にPath.Combineを使用(セパレータを処理)。Path.GetTempFileNameがユニークな一時ファイルを作成。FileInfo/DirectoryInfoはプロパティ(Length、CreationTime)とメソッド(CopyTo、MoveTo、Delete)を持つファイル/ディレクトリ操作のオブジェクト指向ラッパー。パスには文字列操作の代わりにこれらを使用。

vb
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)で動作 — しかしカンマを含むクォートフィールド("Smith, John")で壊れる。TextFieldParser(Microsoft.VisualBasic.FileIO内)はクォート、エスケープクォート、デリミタを正しく処理 — 実際のCSVに使用。固定幅ファイル(FieldWidths)も解析。CSV書き込み時、カンマ/クォートを含むフィールドを手動でクォート。My.Computer.FileSystemはVB固有の便利なラッパー(シンプルなAPI、制御は少ない)。大規模CSVにはCsvHelper(NuGet)を検討 — 堅牢でストリーミングCSVライブラリ。ヘッダ行は常に別途処理。

vb
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})

非同期ファイル操作

非同期ファイルI/OはAwait付きのReadToEndAsync/ReadLineAsync/WriteAsyncを使用 — I/O中スレッドがブロックされずレスポンシブ性が向上(特にUIアプリ)。非同期メソッドはTaskまたはTask(Of T)を返す;Awaitが結果をアンラップ。多数のファイルを並行処理する場合、すべてのタスクを開始しAwait Task.WhenAll(並列I/O)。Async Subはイベントハンドラ用;それ以外はAsync Function。非同期I/OはWebサーバー(多数リクエスト処理)とデスクトップアプリ(UI応答性維持)で威力を発揮。オーバーヘッドは小さいので、遅くなる可能性のあるI/Oには非同期を使用。

vb
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 Function
09

Windows FormsとUI

フォームの基礎とイベント

Windows Forms(WinForms)はFormにControlsを追加してデスクトップUIを構築。各コントロールはプロパティ(Text、Location、Size)とイベント(Click、Load、FormClosing)を持つ。AddHandlerがランタイムでイベントをワイヤリング;Handles(WithEvents付き)がコンパイル時にワイヤリング。MessageBox.Showがダイアログを表示。OnLoad/OnFormClosingはフォームライフサイクル用のオーバーライド可能なprotectedメソッド。Application.Runがメッセージループを開始。WinFormsはデザイナを使用(Visual Studioでドラッグ&ドロップ) — 生成コードは.Designer.vb部分クラスに配置。現代アプリにはWPFまたはWinUIを検討。

vb
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(ドロップダウン)、NumericUpDown(スピナー付き数値入力)、ListBox(選択可能リスト)。Location(Point)とSizeを設定。簡潔なセットアップにオブジェクト初期化子(With {.Prop = value})を使用。Controls.AddRangeで複数コントロールを一度に追加。各コントロールはデフォルトイベント(Button.Click、TextBox.TextChanged、ComboBox.SelectedIndexChanged)を持つ — デザイナでダブルクリックしてハンドラを生成。

vb
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 Class

レイアウト:パネル、ドッキングとアンカリング

WinFormsレイアウト:Dock(Top/Bottom/Left/Right/Fill)がコントロールを端にフィル — Fillは最後に追加して残りスペースを取る。Anchorがコントロールを親の端にピン留め(フォームリサイズ時にリサイズ)。TableLayoutPanelはグリッドにコントロールを配置(パーセント/絶対サイズの行/列) — フォームに最適。FlowLayoutPanelはフローでコントロールを積み上げ(自動折り返し)。Panelはグループ化用のシンプルなコンテナ。レスポンシブレイアウトには手動配置よりTableLayoutPanelを推奨。Controls.Addの順序はドッキングに影響(後の追加が前を重ねる)。

vb
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は"説明|パターン|説明|パターン"形式を使用。ShowDialogはDialogResult(OK/Cancel)を返す — 結果使用前に常にチェック。ダイアログはUsingでラップ(IDisposable)。OverwritePromptが誤上書きを防止。InitialDirectoryがダイアログを有用な場所で開始。これらのダイアログはカスタムコードなしでネイティブWindows UIを提供。

vb
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 Class

データバインディングとDataGridView

データバインディングがUIコントロールをデータオブジェクトに自動接続。単純バインディング:txt.DataBindings.Add("Text", obj, "PropName")。リストにはDataGridView.DataSourceにBindingList(Of T)またはDataTableを設定。INotifyPropertyChangedが双方向バインディングを有効化 — プロパティ変更時にUIが更新(setterでPropertyChangedを発生)。BindingList(Of T)はObservableCollectionのように — アイテム追加/削除時にグリッドに通知。BindingSourceがナビゲーションとフィルタを追加。AutoGenerateColumnsがプロパティから列を作成。データバインディングでUIとデータ間の手動同期コードを排除。

vb
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
10

ファイルI/Oディープダイブ

StreamReaderとStreamWriter

StreamReader/StreamWriterが適切なエンコーディングと破棄でテキストファイルを処理。Usingブロックが例外時でもファイルをクローズ — 常に使用。File.ReadAllLines/ReadAllTextは小ファイルに便利;ループ内のReadLineは大ファイルにメモリ効率的。非同期メソッド(ReadAllTextAsync)がI/O中のUIフリーズを防止。append:=True付きStreamWriterが既存ファイルに追加。デフォルトエンコーディングはUTF-8;レガシー形式にはNew StreamWriter(path, append, Encoding.UTF8)で明示的に指定。

vb
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がカンマを含むクォートフィールドを正しく処理(単純なSplitと異なる)。本番CSVにはCsvHelper(NuGet)ライブラリを使用 — エッジケース、型マッピング、ストリーミングを処理。バイナリファイルはプラットフォーム固有(エンディアン);クロスプラットフォーム互換性にはリトルエンディアンでBinaryWriterを使用。

vb
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がより効率(1回のシステムコール)。EnumerateFilesは遅延(1つずつyield)vs GetFilesはすべてのパスをメモリにロード — 大ディレクトリにはEnumerateを使用。Path.Combineがクロスプラットフォームで正しいセパレータでパスを構築;手動で文字列を結合しない。SearchOption.AllDirectoriesはアクセス拒否でスロー;UnauthorizedAccessExceptionをキャッチまたは再帰ヘルパーを使用。

vb
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:dataile.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)を処理。

vb
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 Using

非同期ファイル操作とストリーム

非同期ファイルI/Oが大操作中のUIを応答性に保つ。ReadAsync/WriteAsyncがきめ細かい進捗報告のためにバッファで動作。IProgress(Of T)がUIスレッドに安全に進捗を報告。MemoryStreamはインメモリデータ用(ディスクなし)。GZipStreamがストリームをオンザフライで圧縮/展開。ネットワークI/OにはHttpClient(非同期)を使用。ストリームがクローズされることを保証するため常にUsingブロックを使用。バッファサイズ81920(80KB)が良いデフォルト — メモリとシステムコールオーバーヘッドのバランス。

vb
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
11

ADO.NETデータベースアクセス

接続とコマンド

SQLインジェクションを防ぐため常にパラメータ化クエリ(Parameters.AddWithValue)を使用 — ユーザー入力をSQL文字列に連結しない。SqlConnection、SqlCommand、SqlDataReaderはすべてIDisposable;Usingブロックでラップ。ExecuteReaderが行を返す;ExecuteNonQueryが影響行数を返す(INSERT/UPDATE/DELETE);ExecuteScalarが単一値を返す(COUNT、SUM)。他のデータベースには適切なプロバイダ(OracleConnection、OleDbConnection、OdbcConnection)または汎用DbProviderFactoryを使用。

vb
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 Using

DataSetとDataTable

DataTable/DataSetは切断データコンテナ — 一度ロードし、オフラインで作業、後で更新。SqlDataAdapter.Fillがデータをロード;SqlCommandBuilderが単純な単一テーブルシナリオ用のINSERT/UPDATE/DELETEコマンドを自動生成。型付きDataSet(.xsdデザイナ経由)がコンパイル時型チェックとIntelliSenseを提供。DataViewが元のDataTableを変更せずにフィルタ/ソート。現代アプリケーションには生DataSetよりEntity FrameworkまたはDapperを推奨 — より保守可能でテスト可能。DataSetはレポートとレガシー相互運用に依然として有用。

vb
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が1接続の複数コマンドをラップ。TransactionScopeが複数接続/リソースにまたがる分散トランザクションを有効化(必要に応じてMS DTCを使用) — コミットにscope.Complete()を呼び出す。接続プーリングはADO.NETで自動 — 接続は再作成されず再利用され、パフォーマンスが劇的に向上。接続をプールに戻すため常にClose/Dispose(Usingブロック)。高スループットアプリには接続文字列でプールサイズとライフタイムを設定。

vb
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 pool

非同期データベース操作

非同期データベース操作(OpenAsync、ExecuteReaderAsync、ReadAsync)が長クエリ中のUIフリーズを防止。パターンは同期ADO.NETと同じだがAwait付き。UIアプリケーションでは応答性を維持するため常にAsyncを使用。高スループットサーバーでは、非同期DB呼び出しがスレッドを解放し他のリクエストを処理。MARS(Multiple Active Result Sets)が1接続で複数リーダーを許可 — 接続文字列で'MultipleActiveResultSets=True'で有効化。データベース固有エラー(デッドロック、制約違反)のためにSqlExceptionを処理。

vb
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 Sub

DapperマイクロORM

DapperはIDbConnectionを拡張メソッドで拡張するマイクロORM — 高速(生ADO.NETに近い速度)でシンプル。Query<T>が列名をプロパティにマッチして行をオブジェクトに自動マッピング。匿名オブジェクトがパラメータを提供(SQLインジェクションセーフ)。バルクインサートはリストを渡しDapperがアイテムごとに1回実行。マルチマッピングが列で行を分割して結合を処理。Dapperは生ADO.NETより少ないボイラープレートでSQL制御を望む場合に最適。複雑なオブジェクトグラフと変更追跡にはEntity Frameworkを使用。

vb
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)
12

LINQ to Objects

クエリとメソッド構文

LINQは2つの構文を提供:クエリ(SQL風、From...Where...Select)とメソッド(流暢、.Where().Select())。両者は同じILにコンパイル — 可読性で選択。クエリ構文はより少ない演算子をサポート(Sum、Count直接なし);それらにはメソッド構文を使用。LINQは遅延実行を使用 — クエリは反復(For Each)または実体化演算子(ToList、Count)を呼ぶまで実行されない。これはクエリ定義後にソースを変更すると結果に影響することを意味。.ToList()で即座実行を強制しデータをスナップショット。

vb
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を持ちそれ自体列挙可能。クエリ構文'Group By...Into Group'はVB固有。グループごとの複数集約はレポートに一般的。Any/Allは短絡(早期停止) — 完全反復なしで条件チェックに便利。集約関数は空シーケンスでスロー;null許容バリアント(Sumは0を返す、Averageはスロー)またはDefaultIfEmptyで処理。

vb
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)
    })

結合、Zipと集合操作

Joinが内部結合(キーマッチ);DefaultIfEmpty付きGroup Joinが左結合を作成。Zipが要素を位置ごとにペアリング。集合操作(Union、Intersect、Except、Distinct)はデフォルト等価比較子を使用 — 複雑型にはEquals/GetHashCodeをオーバーライドまたはカスタムIEqualityComparerを渡す。Concatは重複を除去せず追加(Unionと異なる)。これらは実体化時以外遅延。大規模データセットには、JoinのO(n*m)ネストループの代わりにO(1)ルックアップのHashSetまたはDictionaryを検討。

vb
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が降順ソート。複数ソートキーはOrderBy後にThenByを使用。ページングはSkip(オフセット)とTake(制限) — UIの大規模データセットに不可欠。First/Lastは空でスロー;FirstOrDefaultはデフォルト値を返す(参照型はNothing、数値は0)。Singleはちょうど1マッチを強制(それ以外はスロー) — 検証に便利。TakeWhile/SkipWhileが述語に基づいて失敗するまで分割。First/Last/Singleは反復を強制する以外は遅延。

vb
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)はデリゲートを使用(インメモリ);IQueryable(Of T)はSQLに変換する式ツリーを使用(EF/LINQ to SQL) — 混ぜるとクライアント側評価(遅い)を引き起こす。ToList/ToArray/ToDictionaryが評価を強制し結果をスナップショット。PLINQ(.AsParallel)がクエリを並列化 — 大コレクションのCPUバウンド操作に使用、ただし順序付けとスレッドセーフに注意。

vb
' 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()
13

マルチスレッドと非同期

TaskとAsync/Await

Async/Awaitは非同期コードを書く現代的な方法 — 同期的に見えるがスレッドをブロックしない。メソッドをAsyncでマークしTask返し呼び出しにAwaitを使用。UIイベントハンドラはAsync Subに可能(Async Subが許容される唯一の場所)。Task.RunがCPUバウンド作業をスレッドプールにオフロード。Task.WhenAllがすべてのタスクを待機(並列);Task.WhenAnyが最初の完了を待機。コンパイラが継続、例外伝播、コンテキストキャプチャを処理するステートマシンを生成。手動スレッドやコールバックより常にAsync/Awaitを推奨。

vb
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#)が相互排除を提供 — 一度に1スレッドのみブロックに入る。プライベートObjectでロック、Meや文字列では決して。Interlockedがロックなしのアトミック操作を提供(単純ケースより高速)。Mutexはプロセス間で動作(シングルインスタンスアプリ)。SemaphoreSlimが並行アクセスを制限(例:最大3 API呼び出し)。ロック/セマフォは常にFinallyブロックで解放。デッドロックは2スレッドが互いに待機する時に発生 — 一貫した順序でロック取得。ConcurrentDictionary、ConcurrentQueueがロックフリーのスレッドセーフコレクション。

vb
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()

並列とBackgroundWorker

Parallel.For/ForEachがスレッドプールスレッドに作業を分割 — CPUバウンドループに最適。PLINQ(.AsParallel)がLINQクエリを並列化。ParallelOptions.MaxDegreeOfParallelismでスレッドを制限。CancellationTokenが協調キャンセルを有効化。BackgroundWorkerはレガシーだがWinFormsに便利 — ProgressChangedとRunWorkerCompletedをUIスレッドに自動マーシャル。新規コードにはTask.Run + IProgress(Of T) + Async/Awaitを推奨。並列ループはブロッキング(Asyncと異なる);UIスレッドで使用しない。

vb
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には3つのTimerがある:Windows.Forms.Timer(UIスレッド、フォームに最もシンプル)、System.Threading.Timer(スレッドプール、軽量)、System.Timers.Timer(コンポーネント、サーバーシナリオ)。Forms.TimerのみUIを直接更新可能;他はInvokeが必要。Control.InvokeがデリゲートをUIスレッドにマーシャル — 最初にInvokeRequiredをチェック。ConfigureAwait(False)がライブラリコードでパフォーマンスを向上(コンテキストキャプチャなし)だがその後のUIアクセスを防止。SynchronizationContext.Postが特定コンテキスト(UI、ASP.NET)にマーシャルするより一般的な方法。

vb
' 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)

並行コレクションとチャネル

並行コレクション(ConcurrentDictionary、Queue、Stack、Bag)はレギュラーコレクションのスレッドセーフな代替 — ロックの代わりにこれらを使用。ConcurrentDictionaryのAddOrUpdate/GetOrAddはアトミックな複合操作。BlockingCollectionがブロッキングAdd/Takeでproducer-consumerパターンを実装 — ワーカーキューに最適。Channels(System.Threading.Channels)は現代的で非同期フレンドリーな代替でバックプレッシャー付き。UIスレッドセーフには並行コレクションよりInvoke/BeginInvokeを使用。手動ロックよりこれらの組み込みプリミティブを常に推奨 — テストされ最適化されている。

vb
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()
14

レジストリとWindows API

レジストリ読み書き

Windowsレジストリはアプリケーション設定とシステム設定を格納。アクセスにはMicrosoft.Win32.Registryを使用。HKEY_CURRENT_USER(HKCU)はユーザーごと(管理者権限不要);HKEY_LOCAL_MACHINE(HKLM)はシステム全体(管理者権限必要)。読み取り時に常にデフォルト値を提供(欠落時Nothingを返す)。型を指定するためRegistryValueKindを使用(String、DWord、Binary)。Runキーがログイン時にアプリを自動起動。RegistryKeyをUsingでラップしてハンドルをクローズ。レジストリ編集は慎重に — 間違いはWindowsを壊す可能性。

vb
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がDllImport経由でアンマネージWindows API関数を呼び出し可能。C APIに一致する関数シグネチャを宣言;ランタイムが型を自動マーシャリング(String ↔ LPSTR、Integer ↔ DWORD)。出力パラメータにはByRef、参照渡し構造体にはStructLayoutを使用。CharSet.AutoがOSに基づきANSIまたはUnicodeを選択。一般的なライブラリ:user32(ウィンドウ、メッセージ)、kernel32(システム、ファイル)、gdi32(グラフィックス)。P/Invokeは常にNativeMethodsクラスにラップ。Pinvoke.netがシグネチャの優れたリソース。

vb
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_CYSCREEN

プロセスとシェル操作

Process.Startが外部プログラムを起動。UseShellExecute=FalseとRedirectStandardOutputで出力をプログラム的にキャプチャ — コマンドラインツールに不可欠。デッドロックを避けるため常にWaitForExitし出力を読み取り(出力バッファが満杯になりプロセスをブロック)。GetProcessesが実行中プロセスを列挙;GetProcessesByNameが特定のものを検索。Verb='runas'が管理者に昇格(UACトリガー)。長時間実行プロセスにはExitedイベントをサブスクライブまたは非同期パターンを使用。ユーザー提供のファイルパスは慎重に — コマンドインジェクションを防ぐため検証。

vb
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がシステムとユーザー情報を提供。SpecialFolder付きGetFolderPathが標準ディレクトリ(AppData、MyDocuments、Temp)を見つける正しい方法 — パスをハードコードしない。環境変数がマシン間で動作を設定。GetCommandLineArgsは実行可能ファイルを最初の要素として含む。詳細なハードウェア情報にはWMI(System.Management)を使用 — Win32_Processor、Win32_LogicalDiskなどのクラスをクエリ。Screen.AllScreens(Windows Forms)がマルチディスプレイセットアップのモニター情報を提供。パスや設定を推測する代わりにこれらのAPIを常に使用。

vb
' 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}")
Next

INIファイルと設定

INIファイルはレガシーだが単純な設定にまだ使用。Windows API関数(WritePrivateProfileString、GetPrivateProfileString)が解析を処理。現代.NETにはapp.config(XML)またはappsettings.json(JSON)をConfigurationManagerまたはMicrosoft.Extensions.Configurationで使用。JSON設定はネスト構造、配列、環境固有オーバーライド(appsettings.Production.json)をサポート。設定ビルダーが複数ソース(ファイル、環境変数、コマンドライン)を結合。デプロイ柔軟性のため設定をコードから常に分離。

vb
' 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 }
15

コレクションとジェネリクス詳細

List、DictionaryとHashSet

List(Of T)が主力 — O(1)追加、O(n)挿入/削除の動的配列。Dictionary(Of K,V)がO(1)キールックアップ(ハッシュテーブル)を提供。HashSet(Of T)が高速集合操作(Union、Intersect、Except)でユニーク要素を格納。SortedDictionaryがキーをソート(二分探索木)。LinkedListは双方向リンク(任意の場所で高速挿入/削除だがインデックスアクセスなし)。アクセスパターンに基づいて選択:インデックス付きはList、キー付きはDictionary、メンバーシップはHashSet、順序付き反復はSortedDictionary。すべてジェネリック(型安全、ボクシングなし)。

vb
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データバインディングに不可欠。BindingListは編集機能(AddingNew、AllowEdit、AllowRemove)でこれを拡張。ReadOnlyCollectionはリストをラップして変更を防止(コピーではなく元を返す)。KeyedCollectionはリストと辞書のセマンティクスを組み合わせ(インデックスまたはキーでアクセス)。スレッドセーフ版にはSystem.Collections.ConcurrentのConcurrentQueue、ConcurrentStack、ConcurrentDictionaryを使用。

vb
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 Class

ジェネリックメソッドと制約

ジェネリクスはボクシング/アンボクシングオーバーヘッドなしで型安全を提供。制約(Of T As ...)が型パラメータを制限:Class(参照型)、Structure(値型)、New(パラメータなしコンストラクタ)、IComparable(インターフェース)、または基本クラス。複数制約は中括弧を使用:Of T As {Class, New, IComparable(Of T)}。ジェネリックメソッドは引数から型を推論。ジェネリクスは.NETで再具現化(Javaの型消去と異なりランタイムで型情報が利用可能)。型安全を維持しながらコード重複を回避するためコレクション、アルゴリズム、ユーティリティクラスにジェネリクスを使用。

vb
' 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)から継承。特別な動作(検証、通知、遅延ロード)が必要な場合にカスタムコレクションが意味。ほとんどのケースでは、スクラッチから構築するより既存コレクションを合成。

vb
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) = ...)と要素ごとの等価性をサポート。内部メソッド戻り値と中間LINQ結果にValueTupleを使用。公開APIには意味のある名前のレコードまたはクラスを推奨。破棄パターン(_)が不要な値を無視。ValueTupleはメソッドから返す必要のある匿名プロジェクションにLINQで特に便利。

vb
' 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
16

デバッグと診断

DebugとTrace

DebugクラスはReleaseビルドでコンパイルアウト(ConditionalAttribute使用);Traceは全ビルドで動作。TraceListenersが出力をファイル、コンソール、イベントログに転送。TraceSwitch(app.configで設定可能)が再コンパイルなしでランタイムに冗長性を制御。TraceSourceが名前付きで細粒度のトレーシングをイベントタイプ(Error、Warning、Info、Verbose)付きで提供。本番診断にはTraceを、開発アサーションにはDebugを常に使用。Conditional属性がReleaseビルドでメソッド呼び出しを完全に削除 — ゼロオーバーヘッド。デプロイ柔軟性のためapp.configでリスナーを設定。

vb
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がステップ動作を制御。条件付きブレークポイントは条件がtrueの時のみ一時停止 — 大ループのバグ発見に不可欠。トレースポイントがコードを変更せずにメッセージをログ。イミディエイトウィンドウがランタイムで式を評価。エディットコンティニューが再起動せずにコードを修正。これらのデバッガ機能でデバッグを劇的に高速化。

vb
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句)がキャッチせずに条件を追加 — フィルタがfalseの場合、例外は伝播。カスタム例外はException(非推奨のApplicationExceptionではなく)から継承し、Serializableで、3つのコンストラクタを実装。実装詳細を抽象化するため低レベル例外をドメイン固有のものでラップ。例外をキャッチしてサイレントに飲み込まない — 最低限ログに記録。グローバル例外ハンドラ(AppDomain.UnhandledException、Application.ThreadException)をセーフティネットとして使用。

vb
' 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イベントログはシステムレベルイベント(サービス開始/停止、重大エラー)に最適 — ソース作成に管理者権限が必要。アプリケーションロギングには構造化ロギングフレームワークを使用:Serilog(現代、JSONフレンドリー)、NLog、log4net。構造化ロギング(logger.Information("User {UserId}...", id))が文字列連結と異なりデータ型をクエリ用に保持。ログレベル(Debug、Info、Warning、Error、Fatal)でランタイムにフィルタ。デバッグ用にログに常にコンテキスト(ユーザーID、注文ID)を含める。パフォーマンスカウンタが本番でCPU、メモリ、カスタムメトリクスを監視。

vb
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でテストを実行し回帰をキャッチ。

vb
' 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)
17

コレクションと辞書

ArrayListとList(Of T)

List(Of T)はArrayListの現代的で型安全な置換。常にこれを推奨 — コンパイル時型チェックとより良いパフォーマンス(値型のボクシングなし)。From句でリストをインライン初期化。FindAll/Findは述語(ラムダ)を使用。AddRangeで複数アイテムを一度に追加。

vb
' 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)だから。

vb
' 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は高速メンバーシップテストと集合操作(union、intersect、except)用。SortedSetはソート順で要素を保持(二分探索木)。両者ともO(1)またはO(log n)操作 — List.Contains(O(n))よりはるかに高速。順序が重要でない場合はHashSet;順序付き反復が必要な場合はSortedSet。

vb
' 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を構築して1行で反転。Peekは次の要素を削除せずに確認。

vb
' 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}

並行コレクション

並行コレクションはスレッドセーフ — レギュラーコレクションのロックの代わりに使用。ConcurrentDictionaryのAddOrUpdateとGetOrAddはアトミック。ConcurrentQueue/Stackはロックフリー。ConcurrentBagは各スレッドが主に自身のアイテムを生成/消費する場合に最速。BlockingCollectionがproducer-consumerキューの標準パターン。

vb
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)
18

ファイルI/O高度編

ストリーム読み書き

破棄を保証するためストリームは常にUsingブロックでラップ(例外時でもファイルをクローズ)。File.ReadAllText/ReadAllLinesは小ファイルに便利。大ファイルにはStreamReaderで行ごとに。非同期I/O(ReadToEndAsync)がUIを応答性に保つ — UIスレッドで同期ファイルI/Oを呼ばない。

vb
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を堅牢に解析 — カンマと改行を含むクォートフィールドを処理。Split(",")で独自CSVパーサーを作らない。複雑なCSVにはCsvHelper NuGetパッケージを検討。

vb
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(ストリーミング)を使用。

vb
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イベントを発火(書き込み、フラッシュ、メタデータ) — 最後のイベント時刻を追跡してデバウンス。エラー(イベント多すぎによるバッファオーバーフローなど)も処理が必要。

vb
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)を直接シリアライズ。WriteIndented付きJsonSerializerOptionsが人間可読な出力を生成。

vb
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)
19

エラー処理ディープ

Try/Catchパターン

汎用の前に具体的な例外をキャッチ(順序が重要)。クリーンアップにFinallyを使用 — Try内でreturnやthrowしても実行。Whenフィルタ(VB 14+)で例外プロパティに基づいてキャッチ。例外をラップする際、スタックトレースを保持するため元をinnerExceptionとして渡す。再スローしない限りExceptionをキャッチしない。

vb
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(またはドメイン固有の基本)から継承し、複数コンストラクタを提供:メッセージのみ、メッセージ + 内部、ドメイン固有。AppDomain境界を越える可能性がある場合は<Serializable>でマーク。呼び出し元が必要なコンテキストのプロパティを追加。明確なロギングのためにToStringをオーバーライド。

vb
' 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

例外処理戦略

例外をサイレントに飲み込まない — 最低限ログに記録。IDisposableリソースにUsingを使用(try/finallyよりクリーン)。グローバルハンドラをAppDomain.UnhandledException(最後のチャンス)とApplication.ThreadException(WinForms UIスレッド)に登録。AggregateExceptionが並列コードの複数例外をラップ — フラット化して各InnerExceptionを処理。

vb
' 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を使用。

vb
' 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、操作名)を含める。

vb
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
20

ADO.NETデータベース

接続とコマンド

SQLインジェクションを防ぐため常にパラメータを使用(文字列連結ではなく)。Usingブロックが例外時でも接続をクローズ。SELECTにExecuteReader;INSERT/UPDATE/DELETEにExecuteNonQuery;単一値(COUNTやSCOPE_IDENTITY)にExecuteScalar。接続プーリングは自動 — 遅く開き、早く閉じる。

vb
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

トランザクション

トランザクションが原子性を保証 — すべての操作が成功するかすべて失敗。1データベースには単一接続でBeginTransaction。TransactionScopeが複数接続(さらには複数データベース)にまたがる分散トランザクションを処理 — コミットにCompleteを呼ぶ。分離レベルを慎重に選択:Serializableは最も安全だが最も遅い;ReadCommittedがデフォルト。

vb
Dim connStr = "Server=localhost;Database=mydb;Integrated Security=True;"

' basic transaction
Using conn As New SqlConnection(connStr)
    conn.Open()
    Using tran = conn.BeginTransaction()
        Try
            Using cmd As New SqlCommand("", conn, tran)
                cmd.CommandText = "UPDATE Accounts SET Balance = Balance - 100 WHERE Id = 1"
                cmd.ExecuteNonQuery()

                cmd.CommandText = "UPDATE Accounts SET Balance = Balance + 100 WHERE Id = 2"
                cmd.ExecuteNonQuery()

                tran.Commit()
            End Using
        Catch ex As Exception
            tran.Rollback()
            Console.WriteLine($"Transaction rolled back: {ex.Message}")
            Throw
        End Try
    End Using
End Using

' TransactionScope (distributed transactions)
Using scope As New TransactionScope()
    Using conn1 As New SqlConnection(connStr1)
        ' work on db1
    End Using
    Using conn2 As New SqlConnection(connStr2)
        ' work on db2
    End Using
    scope.Complete()  ' commit all
End Using  ' rollback if Complete not called

' isolation levels
Using tran = conn.BeginTransaction(IsolationLevel.Serializable)
    ' ...
End Using

DataTableとDataAdapter

DataTableはインメモリテーブル — データベースから切断。SqlDataAdapterがデータをロードし更新をプッシュバック。SqlCommandBuilderが単純な単一テーブルシナリオ用のINSERT/UPDATE/DELETEコマンドを自動生成。DataViewが再クエリせずにフィルタ/ソート。新規コードには生DataTableよりORM(EF Core、Dapper)を推奨。

vb
Dim connStr = "Server=localhost;Database=mydb;Integrated Security=True;"

' fill DataTable
Dim dt As New DataTable()
Using adapter As New SqlDataAdapter("SELECT * FROM Users", connStr)
    adapter.Fill(dt)
End Using

' access rows
For Each row As DataRow In dt.Rows
    Console.WriteLine($"{row("Id")}: {row("Name")}")
Next

' filter and sort (in-memory)
Dim view As New DataView(dt) With {
    .Sort = "Name ASC",
    .RowFilter = "Age > 25"
}
For Each row As DataRowView In view
    Console.WriteLine(row("Name"))
Next

' modify and update
Dim newRow = dt.NewRow()
newRow("Name") = "Carol"
newRow("Age") = 28
dt.Rows.Add(newRow)

dt.Rows(0)("Age") = 31              ' modify
dt.Rows(1).Delete()                 ' mark for deletion

' push changes back to DB
Using adapter As New SqlDataAdapter("SELECT * FROM Users", connStr)
    Dim builder As New SqlCommandBuilder(adapter)
    adapter.Update(dt)              ' generates INSERT/UPDATE/DELETE
End Using

' LINQ on DataTable
Dim young = From row In dt.AsEnumerable()
            Where row.Field(Of Integer)("Age") < 30
            Select row.Field(Of String)("Name")

DapperマイクロORM

Dapperは生ADO.NET(高速だが冗長)とEF Core(生産的だが遅い)のスイートスポット。列をプロパティに自動マッピング。パラメータに匿名オブジェクトを使用。リストにQuery<T>;1件にQuerySingle<T>;非クエリにExecute。バッチ操作のためにコレクションをExecuteに渡す。マルチマッピングが結合を処理。

vb
Imports Dapper  ' NuGet: Install-Package Dapper

' Dapper is a lightweight ORM that extends IDbConnection

Dim connStr = "Server=localhost;Database=mydb;Integrated Security=True;"
Using conn As New SqlConnection(connStr)
    ' query
    Dim users = conn.Query(Of User)("SELECT * FROM Users WHERE Age > @age",
                                     New With {Key .age = 18}).ToList()

    ' single
    Dim user = conn.QuerySingle(Of User)("SELECT * FROM Users WHERE Id = @id",
                                          New With {Key .id = 5})

    ' execute
    Dim rows = conn.Execute("INSERT INTO Users (Name, Age) VALUES (@name, @age)",
                            New With {Key .name = "Alice", Key .age = 30})

    ' batch insert
    Dim newUsers = {
        New With {Key .name = "Bob", Key .age = 25},
        New With {Key .name = "Carol", Key .age = 28}
    }
    conn.Execute("INSERT INTO Users (Name, Age) VALUES (@name, @age)", newUsers)

    ' multi-result (one-to-many)
    Dim sql = "SELECT * FROM Companies; SELECT * FROM Employees;"
    Using multi = conn.QueryMultiple(sql)
        Dim companies = multi.Read(Of Company)().ToList()
        Dim employees = multi.Read(Of Employee)().ToList()
    End Using

    ' stored procedure
    Dim result = conn.Query(Of User)("sp_GetUsers", New With {Key .role = "admin"},
                                       commandType:=CommandType.StoredProcedure)
End Using

EF Coreの基礎

EF Coreはフル機能ORM — リレーションシップ、マイグレーション、変更追跡、LINQクエリを処理。DbSetプロパティがエントリポイント。SaveChangesがすべての追跡された変更を永続化。Includeが関連エンティティをEagerロード(N+1クエリを回避)。読み取り専用シナリオではより良いパフォーマンスにAsNoTracking()を使用。マイグレーションがスキーマ変更を管理。

vb
Imports Microsoft.EntityFrameworkCore

' DbContext
Public Class AppDbContext
    Inherits DbContext

    Public Property Users As DbSet(Of User)
    Public Property Orders As DbSet(Of Order)

    Protected Overrides Sub OnConfiguring(options As DbContextOptionsBuilder)
        options.UseSqlServer("Server=localhost;Database=mydb;Integrated Security=True;")
    End Sub

    Protected Overrides Sub OnModelCreating(modelBuilder As ModelBuilder)
        modelBuilder.Entity(Of User)().
            HasKey(Function(u) u.Id).
            HasMany(Function(u) u.Orders).
            WithOne(Function(o) o.User).
            HasForeignKey(Function(o) o.UserId)
    End Sub
End Class

' CRUD
Using db As New AppDbContext()
    ' create
    db.Users.Add(New User With {.Name = "Alice", .Age = 30})
    db.SaveChanges()

    ' read
    Dim alice = db.Users.First(Function(u) u.Name = "Alice")
    Dim adults = db.Users.Where(Function(u) u.Age >= 18).ToList()

    ' update
    alice.Age = 31
    db.SaveChanges()

    ' delete
    db.Users.Remove(alice)
    db.SaveChanges()

    ' eager loading
    Dim usersWithOrders = db.Users.Include(Function(u) u.Orders).ToList()

    ' raw SQL
    Dim raw = db.Users.FromSqlRaw("SELECT * FROM Users WHERE Age > {0}", 18).ToList()

    ' transactions
    Using tran = db.Database.BeginTransaction()
        Try
            ' work
            db.SaveChanges()
            tran.Commit()
        Catch
            tran.Rollback()
            Throw
        End Try
    End Using
End Using
21

VBのLINQ

クエリ構文

VBのLINQクエリ構文はSQL風で複雑クエリにしばしばより読みやすい。From ... Where ... Selectが基本パターン。複数From句はクロス結合(Whereでフィルタ)。Group ByがGroupキーワード経由でアクセス可能なグループを作成。JoinにはEqualsキーワードが必要(SQLのOn ... Equals ...のように)。

vb
Dim nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

' query syntax (SQL-like)
Dim evens = From n In nums
            Where n Mod 2 = 0
            Select n
            ' List: 2, 4, 6, 8, 10

Dim squares = From n In nums
              Select n, Square = n * n

Dim pairs = From n In nums
            Where n > 3
            Order By n Descending
            Select n

' with multiple sources
Dim names = {"Alice", "Bob", "Carol"}
Dim ages = {30, 25, 42}
Dim people = From name In names
             From age In ages
             Where age > 26
             Select name, age

' group by
Dim orders = GetOrders()
Dim byCustomer = From o In orders
                 Group o By o.CustomerId Into Group
                 Select CustomerId, Total = Group.Sum(Function(o) o.Amount)

' join
Dim result = From u In users
             Join o In orders On u.Id Equals o.UserId
             Select u.Name, o.Amount

メソッド構文

メソッド構文は拡張メソッドとラムダを使用 — クエリ構文よりコンパクトで構成可能。Function(x) ...がVBのラムダ構文。Firstは空でスロー;FirstOrDefaultはデフォルトを返す(参照型はNothing、Integerは0)。Skip/Takeが標準ページネーションパターン。両構文は同じILにコンパイル。

vb
Dim nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

' method syntax (fluent)
Dim evens = nums.Where(Function(n) n Mod 2 = 0).ToList()
Dim squares = nums.Select(Function(n) n * n).ToList()
Dim sorted = nums.OrderBy(Function(n) n).ToList()
Dim desc = nums.OrderByDescending(Function(n) n).ToList()

' aggregation
Dim sum = nums.Sum()
Dim avg = nums.Average()
Dim min = nums.Min()
Dim max = nums.Max()
Dim count = nums.Count()
Dim countEven = nums.Count(Function(n) n Mod 2 = 0)

' element access
Dim first = nums.First()
Dim firstEven = nums.First(Function(n) n Mod 2 = 0)
Dim maybeFirst = nums.FirstOrDefault(Function(n) n > 100)  ' 0 if not found
Dim single = nums.Single(Function(n) n = 5)

' take/skip
Dim top3 = nums.Take(3).ToList()
Dim skip3 = nums.Skip(3).ToList()
Dim page = nums.Skip(10).Take(5).ToList()  ' pagination

' distinct
Dim unique = nums.Distinct().ToList()
Dim union = nums.Union({11, 12}).ToList()
Dim intersect = nums.Intersect({5, 6, 7}).ToList()
Dim except = nums.Except({5, 6, 7}).ToList()

グループ化と集約

GroupByがIGrouping(Of Key, Element)を返す — 各グループはKeyを持ちそれ自体列挙可能。ToLookupがルックアップテーブルを作成(複数値辞書のように)。匿名型(New With {Key .X = ...})はプロジェクションに最適 — Keyがプロパティをイミュータブルにし等価性の一部に。Aggregateが最も柔軟だが最も読みにくいコンバイナ。

vb
Public Class Order
    Public Property Id As Integer
    Public Property CustomerId As Integer
    Public Property Amount As Decimal
    Public Property Date As DateTime
End Class

Dim orders = GetOrders()

' group by
Dim byCustomer = orders.GroupBy(Function(o) o.CustomerId)
For Each grp In byCustomer
    Console.WriteLine($"Customer {grp.Key}: {grp.Count()} orders, total {grp.Sum(Function(o) o.Amount)}")
Next

' group with projection
Dim summary = orders.GroupBy(Function(o) o.CustomerId).
    Select(Function(g) New With {
        Key .CustomerId = g.Key,
        Key .OrderCount = g.Count(),
        Key .TotalAmount = g.Sum(Function(o) o.Amount),
        Key .AvgAmount = g.Average(Function(o) o.Amount),
        Key .MaxAmount = g.Max(Function(o) o.Amount)
    }).ToList()

' multi-level grouping
Dim byMonth = orders.GroupBy(Function(o) New With {Key o.Date.Year, Key o.Date.Month})
For Each grp In byMonth
    Console.WriteLine($"{grp.Key.Year}-{grp.Key.Month}: {grp.Sum(Function(o) o.Amount)}")
Next

' lookup (like a dictionary of lists)
Dim lookup = orders.ToLookup(Function(o) o.CustomerId)
Dim customerOrders = lookup(5).ToList()  ' all orders for customer 5

' aggregate with seed
Dim total = orders.Aggregate(0D, Function(acc, o) acc + o.Amount)

データ結合

Joinは内部結合(マッチのみ)。DefaultIfEmpty付きGroup Joinが左結合を与える。複数Join句は自然にチェーン。メソッド構文のJoinは4つのラムダを取る:外部キー、内部キー、結果セレクタ。Zipが位置で要素をペア — 便利だが稀。複雑な結合にはクエリ構文が通常メソッド構文より明確。

vb
Public Class User
    Public Property Id As Integer
    Public Property Name As String
    Public Property DepartmentId As Integer
End Class

Public Class Department
    Public Property Id As Integer
    Public Property Name As String
End Class

Dim users = GetUsers()
Dim departments = GetDepartments()

' inner join
Dim result = From u In users
             Join d In departments On u.DepartmentId Equals d.Id
             Select u.Name, Department = d.Name

' method syntax
Dim result2 = users.Join(departments,
                          Function(u) u.DepartmentId,
                          Function(d) d.Id,
                          Function(u, d) New With {Key .Name = u.Name, Key .Dept = d.Name}).ToList()

' group join (left join)
Dim leftJoin = From d In departments
               Group Join u In users On d.Id Equals u.DepartmentId Into Group
               From u In Group.DefaultIfEmpty()
               Select Department = d.Name, User = If(u Is Nothing, "(none)", u.Name)

' multiple joins
Dim detail = From u In users
             Join d In departments On u.DepartmentId Equals d.Id
             Join o In orders On u.Id Equals o.UserId
             Select u.Name, d.Name, o.Amount

' zip (pair two sequences element-by-element)
Dim nums = {1, 2, 3}
Dim words = {"one", "two", "three"}
Dim pairs = nums.Zip(words, Function(n, w) $"{n}={w}").ToList()
' {"1=one", "2=two", "3=three"}

遅延実行

LINQは遅延実行を使用 — クエリは定義時ではなく反復時に実行。これは各For Eachがクエリを再実行することを意味。結果をキャッシュするにはToList/ToArrayを呼ぶ。クエリラムダで副作用を避ける(予測不能に実行)。一部の演算子(Count、First、Any)は即座実行を強制。Any/Allは短絡。

vb
Dim nums = New List(Of Integer) From {1, 2, 3, 4, 5}

' query is NOT executed yet
Dim query = nums.Where(Function(n) n > 2).Select(Function(n) n * 10)

' executes when iterated
For Each n In query
    Console.WriteLine(n)            ' 30, 40, 50
Next

' executes AGAIN on each iteration
For Each n In query
    Console.WriteLine(n)            ' 30, 40, 50 again
Next

' force immediate execution
Dim list = query.ToList()           ' executes once, caches
Dim arr = query.ToArray()
Dim count = query.Count()           ' executes
Dim first = query.First()           ' executes

' side effects in query — be careful!
Dim i = 0
Dim bad = nums.Where(Function(n)
                        i += 1
                        Return n > 2
                     End Function)
' i changes every time you iterate bad

' eager vs deferred operators
'   Deferred: Where, Select, OrderBy, Skip, Take, Distinct
'   Eager:    ToList, ToArray, Count, First, Sum, Max, Any, All

' check if any/all match
Dim hasEven = nums.Any(Function(n) n Mod 2 = 0)
Dim allPositive = nums.All(Function(n) n > 0)
22

マルチスレッド

Taskとasync

Task.Runがスレッドプールスレッドで作業をスケジュール。Async/Awaitが非同期コードを同期的に見せる — メソッドはAwaitでスレッドをブロックせず一時停止。Task.WhenAllがすべてを待機;Task.WhenAnyが最初の完了を返す。長時間実行タスクには常にCancellationTokenを渡し、定期的にThrowIfCancellationRequestedを呼ぶ。

vb
Imports System.Threading.Tasks

' Task creation
Dim t1 = Task.Run(Function() DoWork())
Dim t2 = Task.Run(Function() 42)  ' returns Integer
Dim result = Await t2  ' 42

' async function
Async Function FetchDataAsync() As Task(Of String)
    Using client As New HttpClient()
        Return Await client.GetStringAsync("https://api.example.com")
    End Using
End Function

' call it
Dim data = Await FetchDataAsync()

' parallel execution
Dim tasks = {
    Task.Run(Function() ComputePart1()),
    Task.Run(Function() ComputePart2()),
    Task.Run(Function() ComputePart3())
}
Dim results = Await Task.WhenAll(tasks)

' when any (first to finish)
Dim allTasks = urls.Select(Function(u) FetchAsync(u))
Dim firstDone = Await Task.WhenAny(allTasks)
Dim firstResult = Await firstDone

' continuation
Dim t = Task.Run(Function() 42).
    ContinueWith(Function(prev) prev.Result * 2)
Console.WriteLine(t.Result)  ' 84

' cancellation
Dim cts As New CancellationTokenSource()
Dim token = cts.Token
Dim task = Task.Run(Function()
    For i = 1 To 100
        token.ThrowIfCancellationRequested()
        Thread.Sleep(100)
    Next
End Function, token)
cts.CancelAfter(500)  ' cancel after 500ms

並列ループ

Parallel.For/ForEachがスレッドプールスレッドに作業を分割 — CPUバウンドループに最適。スレッドローカルオーバーロードは毎回反復でロックせずに還元(sum、max)用。state.Breakがより高い反復を停止;state.Stopが即座に停止。PLINQ(AsParallel)がLINQを並列化 — ただしCPU集約的操作にのみ助けになる。

vb
Imports System.Threading.Tasks

' Parallel.For
Parallel.For(0, 100, Sub(i)
    ProcessItem(i)
End Sub)

' with options
Dim opts As New ParallelOptions With {
    .MaxDegreeOfParallelism = Environment.ProcessorCount
}
Parallel.For(0, 100, opts, Sub(i)
    ProcessItem(i)
End Sub)

' Parallel.ForEach
Dim items = GetItems()
Parallel.ForEach(items, Sub(item)
    ProcessItem(item)
End Sub)

' with state (thread-local)
Dim total As Integer = 0
Dim lockObj As New Object()
Parallel.For(0, 1000,
    Function() 0,  ' local init
    Function(i, state, localTotal)
        localTotal += Compute(i)
        Return localTotal
    End Function,  ' body
    Sub(localTotal)
        SyncLock lockObj
            total += localTotal
        End SyncLock
    End Sub  ' local finally
)

' break vs stop
Parallel.For(0, 100, Sub(i, state)
    If i = 50 Then state.Break()  ' stop iterations > 50
    If i = 25 Then state.Stop()   ' stop all immediately
End Sub)

' PLINQ
Dim nums = Enumerable.Range(1, 1000)
Dim squares = nums.AsParallel().
    Where(Function(n) n Mod 2 = 0).
    Select(Function(n) n * n).
    ToList()

同期

SyncLockが最もシンプルな同期 — 相互排除。Interlockedはアトミックint/long操作用(ロックより高速)。Mutexは名前付きでプロセス間で共有可能(シングルインスタンスアプリ)。Semaphoreが並行性を制限(例:最大3接続)。ReaderWriterLockSlimが複数リーダーまたは1ライターを許可 — 読み込み多いキャッシュに最適。

vb
Imports System.Threading

' SyncLock (Monitor.Enter/Exit)
Private ReadOnly lockObj As New Object()
Private counter As Integer = 0

Sub Increment()
    SyncLock lockObj
        counter += 1
    End SyncLock
End Sub

' Interlocked (atomic operations)
Interlocked.Increment(counter)
Interlocked.Decrement(counter)
Interlocked.Add(counter, 10)
Interlocked.Exchange(counter, 0)
Dim oldVal = Interlocked.CompareExchange(counter, 42, 0)  ' if 0, set to 42

' Mutex (cross-process)
Using mtx As New Mutex(False, "Global\MyAppMutex")
    If mtx.WaitOne(0) Then
        ' got the mutex — only one instance runs
    Else
        ' another instance is running
    End If
End Using

' Semaphore (limit concurrent access)
Dim sem As New Semaphore(3, 3)  ' 3 concurrent
sem.WaitOne()
Try
    ' work
Finally
    sem.Release()
End Try

' ReaderWriterLockSlim
Dim rwLock As New ReaderWriterLockSlim()
rwLock.EnterReadLock()
Try
    ' multiple readers
Finally
    rwLock.ExitReadLock()
End Try
rwLock.EnterWriteLock()
Try
    ' exclusive write
Finally
    rwLock.ExitWriteLock()
End Try

チャネルとproducer-consumer

Channel<T>(現代)がproducer-consumerパイプラインを構築する推奨方法 — 完全に非同期、バウンド容量でバックプレッシャーをサポート。ForEachAsync(並列消費)で複数コンシューマが並行読み取り。BlockingCollectionは古い同期の同等物。ConcurrentQueue/Stackは直接使用のためのロックフリーコレクション。

vb
Imports System.Threading.Channels

' create a channel (bounded for backpressure)
Dim channel = Channel.CreateBounded(Of Integer)(100)

' producer
Async Function ProducerAsync() As Task
    For i = 1 To 1000
        Await channel.Writer.WriteAsync(i)
    Next
    channel.Writer.Complete()
End Function

' consumer
Async Function ConsumerAsync() As Task
    Await ForEachAsync(channel.Reader.ReadAllAsync(),
                       Environment.ProcessorCount,
                       Async Function(item)
                           Await ProcessItemAsync(item)
                       End Function)
End Function

' multiple producers/consumers
Dim producers = Enumerable.Range(0, 3).Select(Function(i) ProducerAsync())
Dim consumers = Enumerable.Range(0, 3).Select(Function(i) ConsumerAsync())
Await Task.WhenAll(producers.Concat(consumers))

' BlockingCollection (older API, still useful)
Dim bc As New BlockingCollection(Of Integer)(100)
' producer
Task.Run(Sub()
    For i = 1 To 100
        bc.Add(i)
    Next
    bc.CompleteAdding()
End Sub)
' consumer
Task.Run(Sub()
    For Each item In bc.GetConsumingEnumerable()
        ProcessItem(item)
    Next
End Sub)

' thread-safe queue
Dim queue As New ConcurrentQueue(Of Integer)()
queue.Enqueue(1)
Dim val As Integer
queue.TryDequeue(val)

タイマーとスケジューリング

3つのタイマー型:System.Threading.Timer(スレッドプール、最も効率的)、System.Timers.Timer(サーバーシナリオ、イベント)、Windows.Forms.Timer(UIスレッド、UI更新用)。非同期コードにはタイマーよりTask.Delayがクリーン。リークを防ぐためタイマーを常に破棄。周期非同期作業にはTask.DelayとCancellationToken付きのWhileループが最もクリーンなパターン。

vb
Imports System.Threading

' System.Threading.Timer (background thread)
Dim timer As New Timer(Sub(state)
    Console.WriteLine($"Tick at {DateTime.Now}")
End Sub, Nothing, TimeSpan.Zero, TimeSpan.FromSeconds(5))

' change interval
timer.Change(TimeSpan.Zero, TimeSpan.FromSeconds(10))

' dispose
timer.Dispose()

' System.Timers.Timer (events, can use SynchronizingObject)
Dim eventTimer As New Timers.Timer With {
    .Interval = 5000,
    .AutoReset = True
}
AddHandler eventTimer.Elapsed, Sub(s, e)
    Console.WriteLine("Elapsed")
End Sub
eventTimer.Start()

' UI timer (WinForms — runs on UI thread)
Dim uiTimer As New Windows.Forms.Timer With {.Interval = 1000}
AddHandler uiTimer.Tick, Sub(s, e)
    Label1.Text = DateTime.Now.ToString()
End Sub
uiTimer.Start()

' one-shot delay
Async Function DoLaterAsync() As Task
    Await Task.Delay(5000)  ' 5 seconds
    Console.WriteLine("Done waiting")
End Function

' periodic with cancellation
Async Function PeriodicAsync(token As CancellationToken) As Task
    While Not token.IsCancellationRequested
        DoWork()
        Await Task.Delay(1000, token)
    End While
End Function
23

シリアライズ

System.Text.JsonによるJSON

System.Text.Jsonは.NETに組み込みの現代的で高速なJSONシリアライザ。属性がシリアライズを制御:JsonPropertyNameがリネーム、JsonIgnoreが除外。JsonNamingPolicy.CamelCaseがJavaScript規約に一致。巨大JSONには(文字列ではなく)ストリームにシリアライズしてすべてをメモリにロードするのを回避。JsonDocumentがターゲット型なしで解析。

vb
Imports System.Text.Json
Imports System.Text.Json.Serialization

Public Class Person
    Public Property Id As Integer
    Public Property Name As String
    <JsonPropertyName("email")>
    Public Property Email As String
    <JsonIgnore>
    Public Property Password As String
    Public Property CreatedAt As DateTime
End Class

' serialize
Dim p As New Person With {.Id = 1, .Name = "Alice", .Email = "[email protected]", .CreatedAt = Date.Now}
Dim json As String = JsonSerializer.Serialize(p)

' with options
Dim opts As New JsonSerializerOptions With {
    .WriteIndented = True,
    .PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    .DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
}
json = JsonSerializer.Serialize(p, opts)

' deserialize
Dim loaded = JsonSerializer.Deserialize(Of Person)(json)

' collections
Dim people As New List(Of Person) From {p}
Dim jsonList = JsonSerializer.Serialize(people)
Dim loadedList = JsonSerializer.Deserialize(Of List(Of Person))(jsonList)

' dynamic (JsonDocument)
Using doc As JsonDocument = JsonDocument.Parse(json)
    Dim name = doc.RootElement.GetProperty("name").GetString()
    Console.WriteLine(name)
End Using

' stream (large files)
Using fs As New FileStream("data.json", FileMode.Create)
    JsonSerializer.Serialize(fs, p)
End Using

XMLシリアライズ

XmlSerializerは冗長だが柔軟。属性が要素/属性名と構造を制御。パラメータなしコンストラクタと公開読み取り/書き込みプロパティが必要。XmlArray/XmlArrayItemがネストコレクションを制御。大XMLファイルの読み取りにはXmlSerializer(すべてをメモリにロード)よりXmlReader(ストリーミング)を推奨。

vb
Imports System.Xml.Serialization
Imports System.IO

<XmlRoot("person")>
Public Class Person
    <XmlElement("id")>
    Public Property Id As Integer
    <XmlElement("name")>
    Public Property Name As String
    <XmlAttribute("role")>
    Public Property Role As String
    <XmlIgnore>
    Public Property Password As String
    <XmlArray("orders")>
    <XmlArrayItem("order")>
    Public Property Orders As List(Of Order)
End Class

' serialize
Dim xs As New XmlSerializer(GetType(Person))
Using fs As New FileStream("p.xml", FileMode.Create)
    xs.Serialize(fs, p)
End Using

' with settings
Dim settings As New XmlWriterSettings With {
    .Indent = True,
    .IndentChars = "  ",
    .Encoding = Encoding.UTF8
}
Using fs As New FileStream("p.xml", FileMode.Create),
      xw As XmlWriter = XmlWriter.Create(fs, settings)
    xs.Serialize(xw, p)
End Using

' deserialize
Using fs As New FileStream("p.xml", FileMode.Open)
    Dim loaded = DirectCast(xs.Deserialize(fs), Person)
End Using

' collections
Dim xsList As New XmlSerializer(GetType(List(Of Person)))
Using fs As New FileStream("people.xml", FileMode.Create)
    xsList.Serialize(fs, people)
End Using

CSV

TextFieldParserがクォートCSVフィールドを正しく処理。書き込み時、カンマ、クォート、改行を含むフィールドをクォートで囲み内部クォートを二重化してエスケープ。本番にはCsvHelper(NuGet)を使用 — すべてのエッジケースを処理、カスタムマッピングをサポート、はるかに高速。CSVには常にライブラリを使用;形式には驚くべきエッジケースがある。

vb
Imports Microsoft.VisualBasic.FileIO

' read CSV (handles quoted fields)
Using parser As New TextFieldParser("data.csv")
    parser.TextFieldType = FieldType.Delimited
    parser.SetDelimiters(",")
    parser.HasFieldsEnclosedInQuotes = True

    While Not parser.EndOfData
        Dim fields = parser.ReadFields()
        Console.WriteLine($"{fields(0)}, {fields(1)}")
    End While
End Using

' write CSV (manual, with proper escaping)
Function CsvEscape(s As String) As String
    If s.Contains(",") OrElse s.Contains("""") OrElse s.Contains(vbCrLf) Then
        Return $"""{s.Replace("""", """""")}"""
    End If
    Return s
End Function

Dim lines As New List(Of String)
lines.Add("name,age,email")
For Each p In people
    lines.Add($"{CsvEscape(p.Name)},{p.Age},{CsvEscape(p.Email)}")
Next
File.WriteAllLines("out.csv", lines)

' CsvHelper (NuGet) — production-grade
' Install-Package CsvHelper
Imports CsvHelper
Using reader As New StreamReader("data.csv"),
      csv As New CsvReader(reader, CultureInfo.InvariantCulture)
    Dim records = csv.GetRecords(Of Person)().ToList()
End Using

Using writer As New StreamWriter("out.csv"),
      csv As New CsvWriter(writer, CultureInfo.InvariantCulture)
    csv.WriteRecords(people)
End Using

バイナリとDataContract

DataContractSerializerはXmlSerializerよりバージョン許容性が高い(Order経由で追加/削除フィールドを処理)。高性能バイナリシリアライズにはProtocol Buffers(protobuf-net)またはMessagePackを使用 — 両方ともXML/JSONより10-100倍小さく高速。ニーズに基づいて選択:人間可読(JSON/XML)vs コンパクト/高速(protobuf/messagepack)。

vb
Imports System.Runtime.Serialization
Imports System.IO

' DataContract (version-tolerant)
<DataContract>
Public Class Person
    <DataMember(Order:=1)>
    Public Property Id As Integer
    <DataMember(Order:=2)>
    Public Property Name As String
    <DataMember(Order:=3, IsRequired:=False)>
    Public Property Email As String
End Class

' serialize to XML
Dim ser As New DataContractSerializer(GetType(Person))
Using fs As New FileStream("p.xml", FileMode.Create)
    ser.WriteObject(fs, p)
End Using

' deserialize
Using fs As New FileStream("p.xml", FileMode.Open)
    Dim loaded = DirectCast(ser.ReadObject(fs), Person)
End Using

' Protocol Buffers (protobuf-net, NuGet)
' Install-Package protobuf-net
<ProtoContract>
Public Class Person
    <ProtoMember(1)>
    Public Property Id As Integer
    <ProtoMember(2)>
    Public Property Name As String
End Class

' serialize
Using fs As New FileStream("p.bin", FileMode.Create)
    ProtoBuf.Serializer.Serialize(fs, p)
End Using

' deserialize
Using fs As New FileStream("p.bin", FileMode.Open)
    Dim loaded = ProtoBuf.Serializer.Deserialize(Of Person)(fs)
End Using

' MessagePack (MessagePack-CSharp, NuGet)
' compact, fast, schema-less
Dim bytes = MessagePack.MessagePackSerializer.Serialize(p)
Dim loaded = MessagePack.MessagePackSerializer.Deserialize(Of Person)(bytes)

カスタムコンバータ

カスタムコンバータがSystem.Text.Jsonがネイティブでサポートしない型(DateOnly、TimeSpan、カスタム型)を処理。JsonDerivedType(NET 7+)がポリモーフィズムを処理 — 識別子付きでランタイム型をシリアライズ。ShouldSerialize*メソッドがプロパティを条件付きで含める(XmlSerializerのパターンのように)。コンバータは常にJsonSerializerOptionsに登録。

vb
Imports System.Text.Json
Imports System.Text.Json.Serialization

' custom converter for a type
Public Class DateOnlyConverter
    Inherits JsonConverter(Of DateOnly)

    Public Overrides Function Read(reader As ByRef Utf8JsonReader,
                                    type As Type, options As JsonSerializerOptions) As DateOnly
        Return DateOnly.Parse(reader.GetString())
    End Function

    Public Overrides Sub Write(writer As Utf8JsonWriter, value As DateOnly, options As JsonSerializerOptions)
        writer.WriteStringValue(value.ToString("yyyy-MM-dd"))
    End Sub
End Class

' register globally
Dim opts As New JsonSerializerOptions()
opts.Converters.Add(New DateOnlyConverter())

' or per-property
Public Class Event
    <JsonConverter(GetType(DateOnlyConverter))>
    Public Property Date As DateOnly
End Class

' polymorphic serialization
<JsonDerivedType(GetType(Dog), "dog")>
<JsonDerivedType(GetType(Cat), "cat")>
Public MustInherit Class Animal
    Public Property Name As String
End Class

Public Class Dog
    Inherits Animal
    Public Property Breed As String
End Class

Dim a As Animal = New Dog With {.Name = "Rex", .Breed = "Lab"}
Dim json = JsonSerializer.Serialize(a, GetType(Animal))
' {"$type":"dog","name":"Rex","breed":"Lab"}

' conditional serialization
Public Class User
    Public Property Name As String
    Public Property Password As String

    Public Function ShouldSerializePassword() As Boolean
        Return False  ' never serialize password
    End Function
End Class
24

リフレクション

型検査

リフレクションでランタイムに型を検査可能。コンパイル時型にはGetType(Type);ランタイム型にはobj.GetType()。BindingFlagsがメンバーをフィルタ(Public/NonPublic、Instance/Static)。GetCustomAttributesが型に適用された属性を返す。リフレクションは遅い — 繰り返し使用のためにTypeオブジェクトとMemberInfoをキャッシュ。

vb
Imports System.Reflection

Dim t As Type = GetType(String)
' or: Dim t = obj.GetType()

' basic info
Console.WriteLine($"Name: {t.Name}")
Console.WriteLine($"Full: {t.FullName}")
Console.WriteLine($"Base: {t.BaseType}")
Console.WriteLine($"IsClass: {t.IsClass}")
Console.WriteLine($"IsEnum: {t.IsEnum}")
Console.WriteLine($"IsGenericType: {t.IsGenericType}")

' interfaces
For Each i In t.GetInterfaces()
    Console.WriteLine($"Implements: {i.Name}")
Next

' properties
For Each prop In t.GetProperties()
    Console.WriteLine($"Property: {prop.Name} ({prop.PropertyType.Name})")
Next

' methods
For Each m In t.GetMethods(BindingFlags.Public Or BindingFlags.Instance)
    Console.WriteLine($"Method: {m.Name}({String.Join(", ", m.GetParameters().Select(Function(p) p.ParameterType.Name))})")
Next

' fields, constructors, events
t.GetFields()
t.GetConstructors()
t.GetEvents()

' attributes
For Each attr In t.GetCustomAttributes(False)
    Console.WriteLine($"Attribute: {attr.GetType().Name}")
Next

' generic type args
If t.IsGenericType Then
    For Each arg In t.GetGenericArguments()
        Console.WriteLine($"Type arg: {arg.Name}")
    Next
End If

インスタンス作成と呼び出し

Activator.CreateInstanceが型でオブジェクトを作成。MethodInfo.Invokeがメソッドを呼び出し — 引数をObject()として渡す。PropertyInfo.GetValue/SetValueがプロパティにアクセス。BindingFlags.NonPublicでプライベートメンバーに到達(カプセル化を壊すため控えめに使用)。MakeGenericMethodがランタイムでジェネリックメソッドを構築。これがプラグインシステムの基盤。

vb
Imports System.Reflection

' create instance
Dim t As Type = GetType(List(Of String))
Dim obj As Object = Activator.CreateInstance(t)

' with constructor args
Dim dt As DateTime = Activator.CreateInstance(GetType(DateTime), {2024, 12, 31})

' invoke method
Dim mi As MethodInfo = t.GetMethod("Add")
mi.Invoke(obj, New Object() {"hello"})

' property get/set
Dim pi As PropertyInfo = t.GetProperty("Count")
Dim count As Integer = CInt(pi.GetValue(obj))
pi.SetValue(obj, 10)  ' may throw if read-only

' field get/set
Dim fi As FieldInfo = GetType(MyClass).GetField("_private", BindingFlags.NonPublic Or BindingFlags.Instance)
Dim val = fi.GetValue(instance)
fi.SetValue(instance, newValue)

' generic method
Dim method As MethodInfo = GetType(Enumerable).GetMethod("Where")
Dim generic = method.MakeGenericMethod(GetType(Integer))
Dim filtered = generic.Invoke(Nothing, New Object() {nums, predicate})

' create delegate from method
Dim del As [Delegate] = [Delegate].CreateDelegate(GetType(Func(Of String, Boolean)), mi)

' load assembly
Dim asm As Assembly = Assembly.LoadFrom("plugin.dll")
Dim pluginType As Type = asm.GetType("MyPlugin")
Dim plugin As Object = Activator.CreateInstance(pluginType)

属性

カスタム属性が型とメンバーにメタデータを追加。AttributeUsageが適用先と複数許可かを制御。GetCustomAttribute/GetCustomAttributesがリフレクション経由でランタイムにそれらを読み取る。組み込み属性:Obsolete(コンパイラ警告/エラー)、Conditional(コンパイル時包含)、Serializable。属性が検証、ORMマッピング、シリアライズなどを駆動。

vb
Imports System

' define custom attribute
<AttributeUsage(AttributeTargets.Class Or AttributeTargets.Method, AllowMultiple:=True)>
Public Class DescriptionAttribute
    Inherits Attribute

    Public ReadOnly Property Text As String
    Public Sub New(text As String)
        Me.Text = text
    End Sub
End Class

' apply
<Description("User service")>
Public Class UserService
    <Description("Get user by ID")>
    Public Function GetUser(id As Integer) As User
        ' ...
    End Function
End Class

' read attributes
Dim t As Type = GetType(UserService)
Dim classAttr = CType(Attribute.GetCustomAttribute(t, GetType(DescriptionAttribute)), DescriptionAttribute)
Console.WriteLine(classAttr?.Text)

For Each mi In t.GetMethods()
    Dim methodAttr = CType(Attribute.GetCustomAttribute(mi, GetType(DescriptionAttribute)), DescriptionAttribute)
    If methodAttr IsNot Nothing Then
        Console.WriteLine($"{mi.Name}: {methodAttr.Text}")
    End If
Next

' built-in attributes
<Obsolete("Use NewMethod instead", True)>  ' True = error, False = warning
Sub OldMethod()
End Sub

<Conditional("DEBUG")>
Sub LogDebug(msg As String)
    Console.WriteLine(msg)
End Sub

<Serializable>
Public Class MyData
End Class

Emitと動的コード

Reflection.EmitがランタイムにILを生成 — 強力だが低レベル。DefineDynamicAssembly -> Module -> Type -> Method -> ILGenerator。EmitがILオペコードを書く。式ツリーはより高レベルな代替 — 式をデータとして構築、デリゲートにCompile。極限パフォーマンス(カスタムシリアライザ)にEmitを使用;動的クエリ(EF Core)に式ツリーを使用。

vb
Imports System.Reflection
Imports System.Reflection.Emit

' build a dynamic assembly/module/type/method
Dim asmName As New AssemblyName("DynamicAsm")
Dim asm As AssemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run)
Dim mod As ModuleBuilder = asm.DefineDynamicModule("MainModule")
Dim type As TypeBuilder = mod.DefineType("Calculator", TypeAttributes.Public)

' add a method
Dim mb As MethodBuilder = type.DefineMethod("Add",
    MethodAttributes.Public Or MethodAttributes.Static,
    GetType(Integer), {GetType(Integer), GetType(Integer)})

Dim il As ILGenerator = mb.GetILGenerator()
il.Emit(OpCodes.Ldarg_0)            ' load first arg
il.Emit(OpCodes.Ldarg_1)            ' load second arg
il.Emit(OpCodes.Add)                ' add
il.Emit(OpCodes.Ret)                ' return

Dim createdType As Type = type.CreateType()
Dim result As Integer = CInt(createdType.GetMethod("Add").Invoke(Nothing, {5, 3}))
Console.WriteLine(result)  ' 8

' expression trees (higher-level)
Imports System.Linq.Expressions

Dim xParam As ParameterExpression = Expression.Parameter(GetType(Integer), "x")
Dim yParam As ParameterExpression = Expression.Parameter(GetType(Integer), "y")
Dim body As BinaryExpression = Expression.Add(xParam, yParam)
Dim addFunc As Func(Of Integer, Integer, Integer) =
    Expression.Lambda(Of Func(Of Integer, Integer, Integer))(body, {xParam, yParam}).Compile()
Console.WriteLine(addFunc(5, 3))  ' 8

パフォーマンス考慮

リフレクションは直接呼び出しより100-1000倍遅い。MemberInfoオブジェクトを常にキャッシュ。ホットパスには式ツリー経由でデリゲートにコンパイル — 直接呼び出しにほぼ同等。ソースジェネレータ(現代.NET)がコンパイル時にリフレクションを行い、ランタイムコストを完全に排除。フレームワーク(シリアライザ、ORM、DI)にリフレクションを使用;ビジネスロジックでは避ける。

vb
Imports System.Reflection

' reflection is slow — cache and optimize

' BAD: re-fetch on every call
For Each item In items
    Dim prop = item.GetType().GetProperty("Name")
    Dim name = CStr(prop.GetValue(item))
Next

' GOOD: cache PropertyInfo
Dim propInfo As PropertyInfo = GetType(Item).GetProperty("Name")
For Each item In items
    Dim name = CStr(propInfo.GetValue(item))
Next

' BEST: compile to delegate (10-100x faster)
Dim getter = CreateGetter(Of Item, String)("Name")
For Each item In items
    Dim name = getter(item)
Next

Function CreateGetter(Of T, TResult)(propName As String) As Func(Of T, TResult)
    Dim param = Expression.Parameter(GetType(T), "obj")
    Dim body = Expression.Property(param, propName)
    Return Expression.Lambda(Of Func(Of T, TResult))(body, param).Compile()
End Function

' source generators (modern alternative)
' compile-time reflection — no runtime cost
' Partial Class MyService
'     <GenerateReflection>
'     Public Shared Sub PrintMethods()
'         ' generated code knows the types at compile time
'     End Sub
' End Class

' when to use reflection:
'   - serializers (JSON, XML)
'   - ORMs (mapping columns to properties)
'   - DI containers (constructor injection)
'   - plugin systems
'   - testing frameworks
' avoid for hot paths — cache or compile
25

相互運用

P/Invoke(Win32 DLL呼び出し)

P/InvokeでネイティブDLL関数を呼び出し可能。DllImportがライブラリとオプションを指定。CharSet.Unicodeが文字列マーシャリングを処理。出力パラメータにByRef。StructLayout(Sequential)が構造体がCレイアウトに一致することを保証。SetLastError=TrueでMarshal.GetLastWin32Error()経由で最後のWin32エラーをクエリ可能。ネイティブ呼び出しは常にNativeMethodsクラスにラップ。

vb
Imports System.Runtime.InteropServices

Public Class NativeMethods
    ' MessageBox from user32.dll
    <DllImport("user32.dll", CharSet:=CharSet.Unicode, SetLastError:=True)>
    Public Shared Function MessageBox(hWnd As IntPtr, text As String, caption As String, type As UInteger) As Integer
    End Function

    ' Beep from kernel32
    <DllImport("kernel32.dll")>
    Public Shared Function Beep(freq As UInteger, duration As UInteger) As Boolean
    End Function

    ' GetSystemMetrics
    <DllImport("user32.dll")>
    Public Shared Function GetSystemMetrics(nIndex As Integer) As Integer
    End Function
End Class

' usage
NativeMethods.MessageBox(IntPtr.Zero, "Hello from native!", "Test", 0)
NativeMethods.Beep(440, 500)  ' 440 Hz for 500ms

' constants
Public Const SM_CXSCREEN As Integer = 0
Public Const SM_CYSCREEN As Integer = 1
Dim screenWidth = NativeMethods.GetSystemMetrics(SM_CXSCREEN)

' structures
<StructLayout(LayoutKind.Sequential)>
Public Structure POINT
    Public X As Integer
    Public Y As Integer
End Structure

<DllImport("user32.dll")>
Public Shared Function GetCursorPos(ByRef lpPoint As POINT) As Boolean
End Function

Dim p As POINT
GetCursorPos(p)
Console.WriteLine($"Cursor at ({p.X}, {p.Y})")

COM相互運用

COM相互運用でOfficeや他のCOMライブラリを呼び出し可能。相互運用アセンブリへの参照を追加(またはNuGetを使用)。COMオブジェクトはMarshal.ReleaseComObjectで常に解放 — すぐにガベージコレクションされず、'ゴースト'Excelプロセスを引き起こす。遅延バインディング(CreateObject)はコンパイル時参照を回避するがIntelliSenseと型安全を失う。

vb
Imports System.Runtime.InteropServices
Imports Excel = Microsoft.Office.Interop.Excel

' add reference: Microsoft.Office.Interop.Excel (or via NuGet)

Sub ExportToExcel(data As DataTable)
    Dim app As Excel.Application = Nothing
    Dim wb As Excel.Workbook = Nothing
    Dim ws As Excel.Worksheet = Nothing

    Try
        app = New Excel.Application()
        app.Visible = False
        wb = app.Workbooks.Add()
        ws = CType(wb.Worksheets(1), Excel.Worksheet)

        ' write headers
        For c = 0 To data.Columns.Count - 1
            ws.Cells(1, c + 1) = data.Columns(c).ColumnName
        Next

        ' write data
        For r = 0 To data.Rows.Count - 1
            For c = 0 To data.Columns.Count - 1
                ws.Cells(r + 2, c + 1) = data.Rows(r)(c).ToString()
            Next
        Next

        ws.SaveAs("C:	empexport.xlsx")
    Finally
        ' ALWAYS release COM objects
        If ws IsNot Nothing Then Marshal.ReleaseComObject(ws)
        If wb IsNot Nothing Then
            wb.Close(False)
            Marshal.ReleaseComObject(wb)
        End If
        If app IsNot Nothing Then
            app.Quit()
            Marshal.ReleaseComObject(app)
        End If
    End Try
End Sub

' late binding (no reference needed)
Dim lateApp As Object = CreateObject("Excel.Application")
lateApp.Visible = True
lateApp.Quit()

C#相互運用

VBとC#は自由に互いを呼べる — 同じILにコンパイル。プロジェクト参照または共有ライブラリを追加。大文字小文字の区別(VBは大文字小文字を区別しない)、インデックス構文、ref/out(VBはByRefを使用)に注意。unsafeコード、特定のジェネリクス、パターンマッチングなどのC#機能にVB相当がない — それらの部分にはC#を使用。

vb
' VB and C# interoperate seamlessly in the same project (multi-targeting)
' Add a C# project reference, or use a shared library

' C# code (in CSharpLib.dll):
'   namespace Utils
'   {
'       public class Calculator
'       {
'           public int Add(int a, int b) => a + b;
'           public static int Multiply(int a, int b) => a * b;
'       }
'   }

' VB usage:
Imports Utils

Dim calc As New Calculator()
Dim sum = calc.Add(2, 3)              ' 5
Dim product = Calculator.Multiply(4, 5)  ' 20

' differences to watch:
'   - VB is case-insensitive; C# is case-sensitive
'   - VB uses () for indexing; C# uses []
'   - VB And/Or are bitwise (AndAlso/OrElse for short-circuit)
'   - C# uses camelCase often; VB PascalCase by convention
'   - C# has unsafe code and pointers; VB doesn't
'   - C# has yield return; VB has Yield (similar)

' consuming async C# methods
Dim result = Await csharpObj.GetDataAsync()

' ref/out parameters
' C#: void TryParse(string s, out int result)
' VB: Integer.TryParse(s, result)  ' result is ByRef

' using C# extension methods
Imports CSharpExtensions
Dim s = "hello".Capitalize()  ' extension method from C# lib

Pythonや他言語の呼び出し

Python相互運用にはPython.NETがCPythonを.NETに埋め込み — 直接オブジェクトアクセス。よりシンプル:Process.Startでpython.exeにシェルアウト。R.NETがRに同じことをする。ClearScriptがJavaScript用にV8を埋め込む。統合の深さに基づいて選択:一度きりスクリプトにはシェルアウト;緊密な統合には埋め込み。パフォーマンスに注意(クロスランタイム呼び出しは遅い)。

vb
' Python via Python.NET (NuGet: Python.Runtime)
' Install-Package Python.NET

' After setup:
' PythonEngine.Initialize()
' Using Py.GIL()
'     Dim np As PyObject = Py.Import("numpy")
'     Dim arr = np.array(New Integer() {1, 2, 3, 4, 5})
'     Dim mean = np.mean(arr)
'     Console.WriteLine(mean)
' End Using

' Or run Python script via process
Dim psi As New ProcessStartInfo With {
    .FileName = "python.exe",
    .Arguments = "script.py arg1 arg2",
    .UseShellExecute = False,
    .RedirectStandardOutput = True,
    .CreateNoWindow = True
}
Using p As Process = Process.Start(psi)
    Dim output = p.StandardOutput.ReadToEnd()
    p.WaitForExit()
    Console.WriteLine(output)
End Using

' R via R.NET (NuGet)
' Install-Package R.NET
' Dim engine = REngine.GetInstance()
' engine.Evaluate("x <- c(1, 2, 3, 4, 5)")
' Dim mean = engine.Evaluate("mean(x)").AsNumeric()[0]

' JavaScript via ClearScript (V8)
' Install-Package Microsoft.ClearScript.V8
' Dim engine As New V8ScriptEngine()
' engine.Execute("function add(a, b) { return a + b; }")
' Dim result = engine.Script.add(2, 3)

' command-line tools
Dim result = Process.Start("ffmpeg", "-i input.mp4 output.wav")

メモリとポインタ

Marshal.AllocHGlobal/FreeHGlobalがアンマネージメモリを割り当て/解放 — 常にTry/Finallyでペアに。Marshal.Copyがマネージとアンマネージメモリ間で配列を移動。VBはunsafeコードをサポートしない(C#を使用、またはMarshalメソッドを使用)。Span(Of T)(現代.NET)がメモリ安全でポインタのようなパフォーマンスを与える — 生ポインタより推奨。

vb
Imports System.Runtime.InteropServices

' allocate native memory
Dim ptr As IntPtr = Marshal.AllocHGlobal(1024)  ' 1KB
Try
    ' copy data
    Marshal.WriteByte(ptr, 0, 42)
    Marshal.WriteInt32(ptr, 4, 1234)

    ' copy array
    Dim arr As Integer() = {1, 2, 3, 4}
    Marshal.Copy(arr, 0, ptr, arr.Length)

    ' read back
    Dim b = Marshal.ReadByte(ptr, 0)
    Dim i = Marshal.ReadInt32(ptr, 4)

    Dim arr2(3) As Integer
    Marshal.Copy(ptr, arr2, 0, 4)
Finally
    Marshal.FreeHGlobal(ptr)
End Try

' string marshaling
Dim strPtr As IntPtr = Marshal.StringToHGlobalUni("hello")
Try
    ' pass to native function
Finally
    Marshal.FreeHGlobal(strPtr)
End Try

' pointer-sized fields
<StructLayout(LayoutKind.Sequential)>
Public Structure HandleInfo
    Public Handle As IntPtr
    Public Size As IntPtr
End Structure

' unsafe code (C# only — VB doesn't support unsafe)
' in C#: unsafe { int* p = &x; *p = 42; }
' in VB: use Marshal class or write a C# helper

' Span(Of T) — modern memory-safe pointer
Dim span As Span(Of Byte) = New Byte(1023) {}
span(0) = 42
Dim intSpan = MemoryMarshal.Cast(Of Byte, Integer)(span)

Was this helpful?