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으로 타입 추론 활성화. Nullable 타입(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

상수, Enum과 구조체

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는 throw 대신 실패 시 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는 단락(성능과 Nothing 참조 방지에 선호). &는 문자열 결합 연산자(+가 아닌, +는 숫자 덧셈 가능). 단락하고 Nothing 객체 검사 같은 오류 피하기 위해 기본적으로 AndAlso/OrElse 사용.

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는 잘못된 입력 시 throw; 안전을 위해 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). 두 인자 If()는 널 병합 연산자: 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는 하나의 값 반환 후 다음 반복에서 중단한 곳에서 재개. 이것은 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) 본문이 최소 한 번 실행 보장 — 메뉴와 입력 검증에 유용. While...End While은 더 오래되고 단순한 형태. Exit Do는 종료; Continue Do는 다음 반복으로 건너뜀. 루프에 진입하지 않을 수 있을 때 Do While; 최소 한 번 실행해야 할 때 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는 변수 이름 반복 없이 하나의 객체의 여러 멤버 접근을 위한 구문적 설탕 — 초기화와 빌더에 유용. 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)는 가독성 향상과 optional 매개변수 건너뛰기 허용.

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 생성자는 타입당 한 번 실행, 정적 데이터 초기화에 이상적.

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 천 단위 구분자, :X 16진수, :D5 0으로 채움, :P 백분율). 날짜 형식: yyyy-MM-dd, HH:mm:ss, dddd(전체 요일 이름). 위치 자리표시자 또는 형식 문자열이 동적으로 구축될 때 String.Format 사용. 문화 인식 앱(국제화)의 경우, ToString/Format에 CultureInfo 전달하여 소수 구분자, 통화 기호, 날짜 형식 제어.

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(공백), +(하나 이상), *(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은 char와 ASCII 코드 간 변환. ToCharArray는 문자열을 가변 char 배열로 변환(문자열 자체는 불변). Encoding.UTF8.GetBytes는 문자열을 바이트 배열로 변환(파일 I/O와 네트워킹에 필수). Base64(Convert.ToBase64String)는 이진 데이터를 텍스트로 인코딩 — data 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) 조회. 예외와 이중 조회 피하기 위해 TryGetValue 사용(ContainsKey + 인덱서). 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 { })는 한 문장으로 컬렉션 생성과 채우기. 튜플(VB 2017+)은 여러 값 그룹화 — 이름 있는 필드(X, Y)가 Item1, Item2보다 명확. 튜플은 클래스 정의 없이 함수에서 여러 값 반환에 완벽. 분해(Dim (a, b) = tuple)은 튜플을 변수로 분할. ToDictionary는 리스트를 사전으로 변환(키 선택자 + 값 선택자). 튜플은 값 타입(struct)이므로 작고 임시 그룹화에 효율적.

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는 단일 상속 지원(하나의 기반 클래스)하지만 여러 인터페이스 가능.

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)

인터페이스와 다형성

인터페이스는 구현 없이 계약(메서드, 속성) 정의 — 클래스가 구현. 상속과 달리 클래스는 여러 인터페이스 구현 가능. Implements 키워드는 멤버를 인터페이스 선언에 연결(VB 특정 문법). 인터페이스는 다형성 활성화: Circle인지 Square인지 모르고 모든 IDrawable로 작업 가능. TypeOf x Is T는 런타임 타입 검사; DirectCast는 캐스팅(잘못되면 throw). 코드 분리를 위해 인터페이스 사용: 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

Partial 클래스와 네임스페이스

Partial 클래스는 클래스를 여러 파일로 분할 — 컴파일러가 병합. 생성된 코드(디자이너 파일)를 수작성 코드와 분리하거나 큰 클래스 분할에 유용. 네임스페이스는 타입을 조직하고 이름 충돌 방지(MyApp.Models.User vs MyApp.Services.User). Imports는 네임스페이스 이름을 스코프로 가져옴(전체 한정 불필요). RootNamespace(프로젝트 설정)는 모든 타입을 감쌈 — 회사/제품 이름으로 설정. 관례: 네임스페이스는 폴더 구조와 일치; 논리적 영역당 하나의 네임스페이스.

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(단일)는 현재 예외 재throw, 스택 추적 보존. When 필터는 조건부로 잡기(Catch...When 조건). 예외 속성: 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와 사용자 정의 예외

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() 호출 보장 — 즉시 가비지 수집되지 않는 리소스(파일, 데이터베이스 연결, 네트워크 스트림)에 중요. 하나의 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) 구체적 예외 타입 throw(ArgumentOutOfRangeException, Exception이 아닌). (5) 잡지 않고 로그하기 위해 When 필터 사용(잡지 않으려면 False 반환). (6) 스택 추적 보존을 위해 Throw(단일)로 재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 배열 반환(전체 파일을 메모리에 로드). 큰 파일의 경우 Using과 함께 StreamReader/StreamWriter 사용 — 줄 단위로 읽고 쓰며, 메모리 낮게 유지. 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) 작동 — but 따옴표로 묶인 쉼표 필드에서 실패("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는 웹 서버(많은 요청 처리)와 데스크톱 앱(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에 Control을 추가하여 데스크톱 UI 구축. 각 컨트롤은 속성(Text, Location, Size)과 이벤트(Click, Load, FormClosing)를 가짐. AddHandler는 런타임에 이벤트 연결; Handles(WithEvents와 함께)는 컴파일 타임에 연결. MessageBox.Show는 대화상자 표시. OnLoad/OnFormClosing은 폼 수명 주기를 위한 재정의 가능 protected 메서드. Application.Run은 메시지 루프 시작. WinForms는 디자이너 사용(Visual Studio에서 드래그 앤 드롭) — 생성된 코드는 .Designer.vb partial 클래스에. 현대 앱의 경우 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 정지 방지. StreamWriter with append:=True는 기존 파일에 추가. 기본 인코딩은 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 with little-endian 사용.

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는 여러 속성이 필요할 때 더 효율적(하나의 시스템 호출). EnumerateFiles는 지연(한 번에 하나씩 산출) vs GetFiles는 모든 경로를 메모리에 로드 — 큰 디렉토리에는 Enumerate 사용. Path.Combine은 크로스 플랫폼으로 올바른 구분자로 경로 구축; 절대 수동으로 문자열 연결하지 마세요. SearchOption.AllDirectories는 접근 거부 시 throw; 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) via NuGet 고려 — 더 많은 기능 but 느림. 신뢰할 수 없는 입력의 경우 항상 역직렬화 오류(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 수정 없이 필터와 정렬. 현대 애플리케이션의 경우, raw 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

트랜잭션과 연결 풀링

트랜잭션은 원자성 보장 — 모든 연산 성공 또는 모두 실패. 단일 연결의 BeginTransaction은 하나의 데이터베이스용. TransactionScope는 여러 연결/리소스에 걸친 분산 트랜잭션 처리(필요 시 MS DTC 사용) — 커밋하려면 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)는 하나의 연결에서 여러 reader 허용 — 연결 문자열에 '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 — 빠르고(raw ADO.NET 속도 근접) 단순. Query<T>는 열 이름을 속성에 매칭하여 행을 객체로 자동 매핑. 익명 객체는 매개변수 제공(SQL 주입 안전). 배치 연산의 경우 리스트를 Execute에 전달하면 Dapper가 항목당 한 번 실행. 멀티 매핑은 조인을 열로 분할하여 처리. Dapper는 raw 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는 두 문법 제공: 쿼리(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은 단락(일찍 중지) — 전체 반복 없이 조건 검사에 유용. 집계 함수는 빈 시퀀스에서 throw; nullable 변형 사용(Sum은 0 반환, Average는 throw) 또는 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는 빈 시 throw; FirstOrDefault는 기본값 반환(참조 타입은 Nothing, 숫자는 0). Single은 정확히 하나의 일치 강제(그렇지 않으면 throw) — 검증에 유용. 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 바운드 연산에 사용, but 순서와 스레드 안전 주의.

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#)은 상호 배제 제공 — 한 번에 하나의 스레드만 블록 진입. private Object에 잠금, Me나 문자열에 절대. Interlocked는 잠금 없는 원자 연산 제공(단순 경우에 더 빠름). Mutex는 프로세스 간 작동(단일 인스턴스 앱). SemaphoreSlim은 동시 접근 제한(예, 한 번에 최대 3 API 호출). 잠금/세마포어는 항상 Finally 블록에서 해제. 교착 상태는 두 스레드가 서로를 기다릴 때 발생 — 일관된 순서로 잠금 획득. 잠금 없는 스레드 안전 컬렉션을 위해 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()

Parallel과 BackgroundWorker

Parallel.For/ForEach는 스레드 풀 스레드에 작업 분할 — CPU 바운드 루프에 이상. PLINQ(.AsParallel)는 LINQ 쿼리 병렬화. ParallelOptions.MaxDegreeOfParallelism으로 스레드 제한. CancellationToken은 협력적 취소 활성화. BackgroundWorker는 레거시지만 WinForms에 편리 — ProgressChanged와 RunWorkerCompleted를 UI 스레드로 자동 마샬링. 새 코드의 경우 Task.Run + IProgress(Of T) + Async/Await 선호. Parallel 루프는 차단(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에는 세 가지 Timer: Windows.Forms.Timer(UI 스레드, 폼에 가장 단순), System.Threading.Timer(스레드 풀, 가벼움), System.Timers.Timer(컴포넌트, 서버 시나리오). Forms.Timer만 UI를 직접 업데이트 가능; 나머지는 Invoke 필요. Control.Invoke는 대리자를 UI 스레드로 마샬링 — 먼저 InvokeRequired 확인. 라이브러리 코드에서 ConfigureAwait(False)는 성능 향상(컨텍스트 캡처 없음) but 이후 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로 생산자-소비자 패턴 구현 — 워커 큐에 좋음. 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)는 DllImport를 통해 VB가 비관리 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 with 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는 이중 연결(어디서나 빠른 삽입/제거, but 인덱스 접근 없음). 접근 패턴 기반 선택: 인덱스는 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 클래스는 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은 단계별 실행 동작을 제어합니다. 조건부 중단점은 조건이 참일 때만 일시 중지합니다 — 큰 루프에서 버그를 찾는 데 필수적입니다. 추적점은 코드 수정 없이 메시지를 로깅합니다. 직접 실행 창은 런타임에 식을 평가합니다. 편집하며 계속하기는 재시작 없이 코드를 수정할 수 있게 합니다. 이러한 디버거 기능을 마스터하면 디버깅 속도가 극적으로 향상됩니다.

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 ex 대신 Throw를 사용하세요. 예외 필터(When 절)는 잡지 않고 조건을 추가합니다 — 필터가 거짓이면 예외가 전파됩니다. 사용자 정의 예외는 Exception(더 이상 사용되지 않는 ApplicationException이 아님)에서 상속하고, Serializable이어야 하며, 세 가지 생성자를 구현해야 합니다. 저수준 예외를 도메인별 예외로 래핑하여 구현 세부 정보를 추상화하세요. 예외를 조용히 삼키지 마세요 — 최소한 로깅하세요. 전역 예외 처리기(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(테스트 주도 개발)는 코드보다 테스트를 먼저 작성합니다. 비즈니스 로직의 높은 커버리지를 목표로 하세요; 사소한 속성 getter/setter는 건너뛰세요. 회귀를 잡기 위해 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(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은 빠른 멤버십 테스트와 집합 연산(합집합, 교집합, 차집합)을 위한 것입니다. 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을 생성하면 한 줄로 역순할 수 있습니다. 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은 생산자-소비자 큐의 표준 패턴입니다.

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으로 전달하세요. 다시 throw하지 않는 한 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

결과 패턴 (함수형)

결과 패턴은 throw 대신 성공/실패를 값으로 반환합니다 — 예외가 비싸고 시끄러운 예상 실패(파싱, 검증)에 유용합니다. 체이닝을 위해 확장 메서드(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

트랜잭션

트랜잭션은 원자성을 보장합니다 — 모든 작업이 성공하거나 모두 실패합니다. 단일 연결의 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>; 하나는 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는 관련 엔티티를 즉시 로드합니다(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은 네 개의 람다를 취합니다: 외부 키, 내부 키, 결과 선택기. 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

멀티스레딩

작업과 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 바운드 루프에 좋습니다. 스레드 로컬 오버로드는 매 반복마다 잠금 없이 축소(합, 최대)를 위한 것입니다. 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은 여러 읽기 또는 한 번의 쓰기를 허용합니다 — 읽기 집약적 캐시에 좋습니다.

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

채널과 생산자-소비자

Channel<T>(현대적)은 생산자-소비자 파이프라인을 구축하는 권장 방법입니다 — 완전히 비동기이며 제한된 용량으로 백프레셔를 지원합니다. 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)

타이머와 스케줄링

세 가지 타이머 타입: 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) 대 소형/빠름(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은 private 멤버에 도달할 수 있게 합니다(신중하게 사용 — 캡슐화 위반). 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 opcode를 작성합니다. 식 트리는 더 고수준의 대안입니다 — 식을 데이터로 빌드한 다음 대리자로 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

Interop

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 interop

COM interop는 Office 및 기타 COM 라이브러리를 호출할 수 있게 합니다. interop 어셈블리에 참조를 추가하세요(또는 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# interop

VB와 C#은 서로 자유롭게 호출할 수 있습니다 — 동일한 IL로 컴파일됩니다. 프로젝트 참조나 공유 라이브러리를 추가하세요. 대소문자 구분(VB는 대소문자 구분 안 함), 인덱싱 구문, ref/out(VB는 ByRef 사용)에 주의하세요. C#의 unsafe 코드, 특정 제네릭, 패턴 매칭 같은 기능은 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 interop의 경우 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?