テックカリキュラム

VB.NET高度文法と.NET内部構造

VB.NET高度文法と.NET内部構造

本章では、VB.NETを単なる「文法としてのプログラミング言語」として扱うのではなく、 .NET基盤上で動作する言語として理解することを目的とします。

VB.NETは、Visual Basicの書きやすさや可読性を引き継ぎながらも、 内部的にはC#などと同じく.NET CLR上で動作する本格的なマネージド言語です。 そのため、VB.NETを高度に扱うには、構文だけでなく、CLR、型システム、メモリ管理、 ジェネリクス、Delegate、Event、LINQ、Nullable、コンパイルオプションなどの仕組みを理解する必要があります。

業務システム開発では、「動くコード」を書くだけでは不十分です。 なぜそのコードが動くのか、どのように実行されるのか、どこでパフォーマンスや保守性の問題が発生するのかを理解して初めて、 長期運用に耐えられるVB.NETアプリケーションを設計できます。


1. VB.NETと.NET CLRの関係

VB.NETは、.NET Frameworkまたは.NET上で動作するプログラミング言語です。 VB.NETで書かれたコードは、直接CPUが実行する機械語に変換されるのではなく、 一度「中間言語」であるILにコンパイルされます。

このILを実行時に処理する基盤が、CLRです。 CLRは、.NETアプリケーションの実行環境であり、メモリ管理、型安全性、例外処理、 セキュリティ、JITコンパイルなどを担当します。

VB.NETのソースコード
        ↓
VBコンパイラ
        ↓
IL(Intermediate Language)
        ↓
CLR
        ↓
JITコンパイル
        ↓
ネイティブコードとして実行

つまり、VB.NETは見た目こそVisual Basicの構文ですが、 実行基盤としては.NETの共通実行環境に乗っています。 このため、C#で作られたライブラリをVB.NETから利用したり、 逆にVB.NETで作成したクラスライブラリをC#から利用したりすることも可能です。

CLRが担う主な役割

  • ILの実行管理
  • JITコンパイル
  • ガベージコレクションによるメモリ管理
  • 型安全性の保証
  • 例外処理の制御
  • アセンブリの読み込みと管理
  • セキュリティチェック

VB.NETを高度に理解するということは、 VBの構文を覚えることではなく、CLR上でコードがどのように動作しているかを理解することです。


2. 値型・参照型・Boxing / Unboxing

.NETの型システムを理解するうえで、値型と参照型の違いは非常に重要です。 VB.NETでは、Integer、Boolean、Date、Decimal、Structureなどは値型として扱われます。 一方、String、Class、Array、Objectなどは参照型として扱われます。

値型とは

値型は、変数そのものが値を保持する型です。 代表例として、Integer、Double、Boolean、Date、Structureなどがあります。

Dim a As Integer = 10
Dim b As Integer = a

b = 20

Console.WriteLine(a) ' 10
Console.WriteLine(b) ' 20

この例では、aの値がbにコピーされています。 そのため、bを変更してもaには影響しません。

参照型とは

参照型は、変数そのものがデータを直接保持するのではなく、 実体への参照を保持する型です。 Classで作成したオブジェクトや配列などが該当します。

Public Class User
    Public Property Name As String
End Class

Dim user1 As New User()
user1.Name = "Tanaka"

Dim user2 As User = user1
user2.Name = "Suzuki"

Console.WriteLine(user1.Name) ' Suzuki
Console.WriteLine(user2.Name) ' Suzuki

この例では、user1とuser2は同じオブジェクトを参照しています。 そのため、user2からNameを変更すると、user1から見た値も変わります。

Boxingとは

Boxingとは、値型をObject型などの参照型として扱うために、 値型の値をヒープ領域上のオブジェクトに包み込む処理です。

Dim number As Integer = 100
Dim obj As Object = number ' Boxing

この処理では、Integer型の値がObject型として扱えるようになります。 ただし、Boxingは内部的に追加のメモリ確保を伴うため、 大量に発生するとパフォーマンス低下の原因になります。

Unboxingとは

Unboxingとは、BoxingされたObject型の値を、元の値型に戻す処理です。

Dim obj As Object = 100
Dim number As Integer = CInt(obj) ' Unboxing

Unboxingでは、実際に格納されている型と取り出す型が一致していない場合、 例外が発生する可能性があります。 そのため、Object型を多用する設計は型安全性を損ないやすく、 保守性や可読性の面でも注意が必要です。

実務上の注意点

  • Object型を安易に使わない
  • ArrayListではなくList(Of T)を使用する
  • 値型を大量にObjectへ代入する設計を避ける
  • パフォーマンスが重要な処理ではBoxingの発生を意識する

3. ジェネリクスの高度利用

ジェネリクスは、型をパラメータとして扱う仕組みです。 VB.NETでは、List(Of T)、Dictionary(Of TKey, TValue)、Nullable(Of T)などで広く利用されています。

ジェネリクスを利用することで、Object型を使った汎用処理に比べて、 型安全性を保ちながら再利用性の高いコードを書くことができます。

Object型による汎用処理の問題

Dim items As New ArrayList()
items.Add("Apple")
items.Add(100)

Dim name As String = CStr(items(1)) ' 実行時エラーの可能性

ArrayListはObject型として値を保持するため、異なる型の値を混在させることができます。 一見便利ですが、実行時まで型の誤りに気付きにくくなります。

ジェネリクスを使った型安全なコレクション

Dim names As New List(Of String)()
names.Add("Apple")
names.Add("Orange")

Dim name As String = names(0)

List(Of String)では、String型以外の値を追加しようとするとコンパイル時にエラーになります。 これにより、実行時エラーを未然に防ぐことができます。

独自ジェネリッククラス

Public Class Repository(Of T)
    Private ReadOnly _items As New List(Of T)()

    Public Sub Add(item As T)
        _items.Add(item)
    End Sub

    Public Function GetAll() As List(Of T)
        Return _items
    End Function
End Class

このように、型を固定せずに汎用的なクラスを作成できます。 たとえば、User用、Product用、Order用など、複数の型に対して同じ構造の処理を再利用できます。

型制約

ジェネリクスでは、使用できる型に制約を付けることも可能です。

Public Class Service(Of T As Class)
    Public Function IsNothingValue(value As T) As Boolean
        Return value Is Nothing
    End Function
End Class

この例では、Tに指定できる型を参照型に限定しています。 型制約を利用することで、ジェネリックなコードであっても、 安全に特定の操作を行えるようになります。

実務での利用例

  • 共通Repositoryクラス
  • 共通APIレスポンス型
  • バリデーション結果クラス
  • 検索条件クラス
  • ページング結果クラス
Public Class ApiResponse(Of T)
    Public Property Success As Boolean
    Public Property Message As String
    Public Property Data As T
End Class

このような設計にすることで、どのようなデータ型を返す場合でも、 共通したレスポンス構造を維持できます。


4. Delegate / Event / Lambda式

Delegate、Event、Lambda式は、VB.NETにおける高度な処理抽象化に関わる重要な機能です。 特に、イベント駆動型アプリケーションや非同期処理、コールバック処理、LINQなどを理解するうえで欠かせません。

Delegateとは

Delegateとは、メソッドを変数のように扱うための型です。 特定の引数と戻り値を持つメソッドへの参照を保持できます。

Public Delegate Function CalculateDelegate(x As Integer, y As Integer) As Integer

Public Function Add(x As Integer, y As Integer) As Integer
    Return x + y
End Function

Dim calc As CalculateDelegate = AddressOf Add
Dim result As Integer = calc(10, 20)

この例では、AddメソッドをDelegateに代入し、calcを通じて呼び出しています。 Delegateを使うことで、処理そのものを引数として渡したり、動的に切り替えたりできます。

Eventとは

Eventは、Delegateを基盤とした通知の仕組みです。 Windows FormsなどのUIアプリケーションでは、ボタンクリックやテキスト変更などがEventとして扱われます。

Public Class ProcessRunner
    Public Event Completed(message As String)

    Public Sub Run()
        ' 何らかの処理
        RaiseEvent Completed("処理が完了しました")
    End Sub
End Class

Eventを利用することで、ある処理が完了したタイミングで、 別の処理を呼び出すような疎結合な設計が可能になります。

Lambda式とは

Lambda式は、名前のない関数を簡潔に記述するための構文です。 LINQやコールバック処理で頻繁に利用されます。

Dim numbers As New List(Of Integer) From {1, 2, 3, 4, 5}

Dim evenNumbers = numbers.Where(Function(n) n Mod 2 = 0)

この例では、Function(n) n Mod 2 = 0 の部分がLambda式です。 「nを受け取り、nが偶数であるかを判定する関数」をその場で定義しています。

DelegateとLambda式の関係

Lambda式は、内部的にはDelegateやExpression Treeとして扱われます。 つまり、Lambda式は単なる省略記法ではなく、 処理をデータのように受け渡すための重要な仕組みです。

実務での利用例

  • ボタンクリックなどのイベント処理
  • コールバック関数
  • LINQによる検索・抽出・集計
  • 非同期処理の完了通知
  • 処理の差し替え可能な設計

5. LINQの内部動作

LINQは、Language Integrated Queryの略で、コレクションやデータソースに対して、 統一的な構文で検索・抽出・変換・集計を行うための機能です。

VB.NETでは、LINQを利用することで、配列、List、DataTable、XML、データベースなどに対して、 読みやすく宣言的なコードを書くことができます。

基本的なLINQの例

Dim numbers As New List(Of Integer) From {1, 2, 3, 4, 5, 6}

Dim result = numbers.Where(Function(n) n Mod 2 = 0)

このコードでは、numbersの中から偶数だけを抽出しています。 Whereメソッドには、各要素を判定するためのLambda式が渡されています。

遅延実行

LINQを理解するうえで特に重要なのが、遅延実行です。 WhereやSelectなどの多くのLINQメソッドは、定義された時点では実際に処理を実行しません。 実際に列挙されたタイミングで処理が実行されます。

Dim numbers As New List(Of Integer) From {1, 2, 3}

Dim query = numbers.Where(Function(n)
                              Console.WriteLine("判定: " & n)
                              Return n Mod 2 = 1
                          End Function)

Console.WriteLine("クエリ定義後")

For Each n In query
    Console.WriteLine(n)
Next

この例では、Whereを定義した時点では「判定」は出力されません。 For Eachでqueryを列挙したタイミングで初めて処理が実行されます。

即時実行

ToList、ToArray、Count、First、Singleなどを使用すると、 LINQの処理はその場で実行されます。

Dim list = numbers.Where(Function(n) n Mod 2 = 1).ToList()

ToListを呼び出した時点で、Whereの条件判定が実行され、結果がListとして確定します。

LINQ利用時の注意点

  • 遅延実行により、意図しないタイミングで処理が走ることがある
  • 同じクエリを複数回列挙すると、そのたびに処理が実行される
  • データベース連携時は、生成されるSQLを意識する必要がある
  • 複雑すぎるLINQは可読性を下げる
  • 大量データではToListのタイミングに注意する

実務での使い分け

LINQは非常に強力ですが、すべての処理をLINQで書けばよいわけではありません。 単純な抽出や変換には有効ですが、複雑な業務ロジックを無理に一行で書くと、 可読性や保守性が低下します。

実務では、LINQを「短く書くための道具」ではなく、 「意図を明確に表現するための道具」として使うことが重要です。


6. Nullable型と型安全設計

業務システムでは、値が存在しない状態を扱う場面が頻繁にあります。 たとえば、退職日、完了日、承認日、任意入力項目などは、 値がまだ存在しない可能性があります。

VB.NETでは、値型にNothingを許可するためにNullable型を利用します。

Nullable型の基本

Dim retiredDate As Date? = Nothing

If retiredDate.HasValue Then
    Console.WriteLine(retiredDate.Value)
Else
    Console.WriteLine("退職日は未設定です")
End If

Date? は Nullable(Of Date) の省略記法です。 通常、Date型は値型であるためNothingを直接表現することはできません。 しかし、Nullable型にすることで、「値がある状態」と「値がない状態」を明確に扱えます。

Nullableを使うべき場面

  • 任意入力項目
  • 未確定の日付
  • データベース上でNULLを許可するカラム
  • 検索条件の未指定状態
  • 計算不能な数値

Nullableを避けるべき場面

  • 必須項目
  • 必ず値が存在する業務キー
  • 初期値と未設定を混同してはいけない項目
  • Nothingを許可すると業務ルールが曖昧になる項目

型安全設計の考え方

型安全設計とは、誤った値や不正な状態をできる限りコンパイル時に防ぐ設計です。 Object型、文字列ベースの状態管理、Nothingの多用などは、 実行時エラーや業務バグの原因になりやすいです。

たとえば、ユーザー区分を文字列で管理すると、タイポや不正値が混入する可能性があります。

Dim userType As String = "Admin"

この場合、”admin”、”ADMIN”、”Admn” のような表記揺れが発生する可能性があります。 このようなケースでは、Enumを使うことで安全性を高められます。

Public Enum UserType
    General
    Admin
    Manager
End Enum

Dim userType As UserType = UserType.Admin

型で表現できるものは、できる限り型で表現する。 これが、VB.NETにおける堅牢な設計の基本です。


7. Option Strict / Option Explicit / Option Inferの実務的使い分け

VB.NETには、コンパイル時の型チェックや変数宣言に関わる重要なオプションがあります。 代表的なものが、Option Strict、Option Explicit、Option Inferです。

これらの設定は、コードの安全性、可読性、保守性に大きく影響します。 特に業務システムでは、暗黙的な型変換や未宣言変数を許可すると、 後から原因を追いにくい不具合につながることがあります。

Option Strict

Option Strictは、暗黙的な縮小変換や遅延バインディングを禁止する設定です。 基本的に、実務開発ではOnにすることが推奨されます。

Option Strict On

Dim value As Double = 10.5
Dim number As Integer = value ' コンパイルエラー

DoubleからIntegerへの変換では、小数点以下が失われる可能性があります。 Option Strict Onでは、このような危険な暗黙変換を禁止します。

明示的に変換する場合は、CIntなどを使用します。

Dim value As Double = 10.5
Dim number As Integer = CInt(value)

Option Explicit

Option Explicitは、変数の宣言を必須にする設定です。 こちらも実務ではOnにするべきです。

Option Explicit On

userName = "Tanaka" ' 宣言されていないためコンパイルエラー

変数宣言を必須にすることで、タイポによるバグを防ぐことができます。

Option Infer

Option Inferは、変数宣言時に型推論を許可する設定です。

Option Infer On

Dim count = 10
Dim name = "Tanaka"

この場合、countはInteger、nameはStringとして推論されます。 型推論を使うことでコードを簡潔にできますが、 複雑な式や戻り値が分かりにくい場面では、明示的に型を書いた方が保守しやすくなります。

実務での推奨設定

Option Strict On
Option Explicit On
Option Infer On

基本的には、上記の設定が推奨されます。 Option Strict OnとOption Explicit Onによって安全性を確保し、 Option Infer Onによって冗長な型宣言を適度に省略できます。

型推論を使ってよい例

Dim users = GetUsers()
Dim activeUsers = users.Where(Function(u) u.IsActive).ToList()

右辺から型が明確に読み取れる場合は、型推論を利用しても問題ありません。

型を明示した方がよい例

Dim result As Dictionary(Of String, List(Of Order)) = GetOrderMap()

戻り値の構造が複雑な場合は、型を明示することで可読性が高まります。 特にチーム開発では、「書いた本人には分かるが、他人には読みにくいコード」を避ける必要があります。


8. VB.NET高度文法を実務設計に活かす視点

ここまで扱った内容は、単なる文法知識ではありません。 すべて実務上の設計判断に直結します。

知識領域実務での影響
CLRの理解実行時挙動、例外、メモリ管理、パフォーマンス理解につながる
値型・参照型データのコピー、参照共有、副作用の理解につながる
Boxing / UnboxingObject型多用による性能劣化や型安全性低下を防げる
ジェネリクス共通処理を型安全に再利用できる
Delegate / Eventイベント駆動や疎結合設計に活用できる
Lambda / LINQデータ操作を宣言的に記述できる
Nullable未設定状態やNULLを安全に扱える
Option設定コンパイル時に多くのバグを防止できる

特にVB.NETの業務システムでは、古いコード資産や暗黙変換に依存したコードが残っていることも多くあります。 そのような環境では、ただ新しい書き方を覚えるだけでなく、 既存コードがなぜ危険なのか、どのように改善すべきかを判断できる力が重要になります。


9. 演習課題

演習1:値型と参照型の違いを確認する

Integer型と独自Class型を使い、代入後に値を変更した場合の挙動の違いを確認してください。

  • Integer型の変数を2つ作成する
  • 片方にもう片方を代入する
  • 代入先の値を変更する
  • 元の変数に影響があるか確認する
  • Class型でも同様の実験を行う

演習2:ジェネリックなRepositoryクラスを作成する

任意の型を扱えるRepository(Of T)クラスを作成し、 Add、Remove、GetAll、Findなどのメソッドを実装してください。

演習3:Delegateを使って処理を切り替える

足し算、引き算、掛け算を行うメソッドを作成し、 Delegateを使って実行する処理を動的に切り替えてください。

演習4:LINQの遅延実行を確認する

Where句の中でConsole.WriteLineを実行し、 クエリ定義時ではなく、For Eachで列挙したタイミングで処理が実行されることを確認してください。

演習5:Nullable型を使った業務項目設計

社員情報クラスを作成し、入社日、退職日、最終ログイン日時などをプロパティとして定義してください。 そのうえで、Nullableにすべき項目と、Nullableにすべきでない項目を整理してください。


10. まとめ

VB.NETを高度に扱うためには、構文を覚えるだけでは不十分です。 VB.NETは.NET CLR上で動作するマネージド言語であり、 その実行モデル、型システム、メモリ管理、コンパイル時チェックの仕組みを理解することで、 より安全で保守しやすいコードを書くことができます。

値型と参照型の違いを理解すれば、副作用や意図しない値の変更を防ぎやすくなります。 BoxingやUnboxingを理解すれば、Object型の多用による性能問題や型安全性の低下を避けられます。 ジェネリクスを活用すれば、共通処理を安全に再利用できます。 Delegate、Event、Lambda式を理解すれば、イベント駆動や処理の抽象化をより柔軟に設計できます。

また、LINQの遅延実行や即時実行を理解することで、 データ操作のタイミングやパフォーマンスを意識した実装が可能になります。 Nullable型を適切に使えば、NULLや未設定状態を明確に表現できます。 さらに、Option Strict、Option Explicit、Option Inferを適切に設定することで、 コンパイル時に多くの不具合を防ぐことができます。

本章のゴールは、VB.NETを「なんとなく書ける状態」から、 「.NET基盤上でどのように動作しているかを理解し、設計判断に活かせる状態」へ引き上げることです。 この理解は、今後学習するオブジェクト指向設計、DB連携、非同期処理、例外処理、 レガシーシステムの改善やモダナイゼーションにおいて、重要な土台になります。