【VB.NET極限最適化】ReadOnly・Const・Staticの使い分け:バグを根絶する変数のライフサイクル制御術
開発現場でこんなコードを見たことはないだろうか。
- 「とりあえず全ての変数を `Dim` で宣言し、どこで書き換わっているか追えない」
- 「メソッド内のカウンターなのに、なぜかインスタンス変数にしてしまっている」
- 「設定値なのにハードコーディングされ、仕様変更のたびにソースコードを全検索している」
業務効率化ツールや社内ニッチシステムにおいて、動けばいいという安易なコードは、数ヶ月後の保守フェーズで必ず地雷と化す。特にVisual Basic / VB.NETは、C#等と比較して記述が柔軟(あるいはルーズ)であるため、開発者の意図を厳格にコードに反映させなければ、簡単に「スパゲッティ・プログラム」が完成してしまう。
今回は、変数の「スコープ(有効範囲)」と「ライフサイクル(生存期間)」を完全に掌握し、意図しない値の書き換えを防ぐための3つの修飾子―― `Const`、`ReadOnly`、`Static` の極意を伝授する。
これらを適材適所で使い分けることこそが、バグの起きない堅牢な業務ロジック構築の第一歩だ。
—
1. 3つの修飾子の本質を見極める
まずは、それぞれの修飾子が「メモリと時間軸において何を意味するのか」を正確に押さえよう。
| 修飾子 | 対象 | 評価・決定タイミング | ライフサイクル | 主な用途 |
| :— | :— | :— | :— | :— |
| `Const` | 定数 | コンパイル時 | アプリケーション実行中永続 | 真に変わらない物理法則的な値(消費税率、最大リトライ回数など) |
| `ReadOnly` | フィールド | 実行時(インスタンス生成時) | オブジェクトの生存期間に依存 | 外部から注入され、後から変更されては困る依存関係や設定値 |
| `Static` | ローカル変数 | 初回実行時 | アプリケーション実行中永続(スコープはメソッド内) | メソッドを跨いでも保持したい状態(キャッシュ、カウンターなど) |
これらを正しく選ぶことは、「不正な状態への遷移を型の力でコンパイルエラーとして弾く」という、モダンな防衛的プログラミングの基本である。
—
2. 【Const】コンパイル時定数 —— 動かぬ値を刻む
`Const` は、コンパイル時に値が確定する。実行時のオーバーヘッドがゼロであり、パフォーマンスと安全性の両面で最強の選択肢だ。ただし、「実行時にしか決まらない値」には使えないという絶対的な制約がある。
⚠️ やりがちなアンチパターン
.net
‘ ✖ 毎回インスタンス生成時に評価される(無駄なオーバーヘッド)
Public Class TaxCalculator
Public theTaxRate As Double = 0.10 ‘ 普通の変数やプロパティ
End Class
⭕ 正しいアプローチ
.net
‘ 〇 コンパイル時に埋め込まれる真の定数
Public Class TaxCalculator
‘ 命名規則としてPascalCaseまたは大文字スネークケースが実務的
Public Const TaxRate As Double = 0.10
Public Function CalculateWithTax(ByVal price As Decimal) As Decimal
Return price (1 + CDec(TaxRate))
End Function
End Class
アーキテクトの視点:
データベースの接続文字列や、ファイルパスの一部など、「環境によって変わり得るもの」に `Const` を使ってはならない。それらは次に解説する `ReadOnly` の領域だ。
—
3. 【ReadOnly】実行時イミュータブル —— 依存性と不変性の担保
実務で最も多用すべきなのが `ReadOnly` だ。クラスのフィールドに対して付与することで、「コンストラクタの実行が完了した瞬間から、二度と値を書き換えられない」ことを保証する。
業務ツールにおいて、DB接続情報やAPIのエンドポイントURL、あるいはDI(依存性の注入)で外部から渡されるサービスなどは、絶対に途中で書き換わっては困る。
実務的プロダクションコード例:ファイル・DB連携クラス
以下のコードは、設定ファイルから読み込んだ接続先情報を保持し、安全にDB操作を行うクラスの模範解答だ。
.net
Imports System.Data.SqlClient
Public Class SecureDatabaseClient
‘ 【重要】一度設定されたら外部から書き換え不能なReadOnlyフィールド
Private ReadOnly _connectionString As String
Private ReadOnly _commandTimeout As Integer
”’
”’
Public Sub New(ByVal connectionString As String, ByVal commandTimeout As Integer)
‘ ここでしか _connectionString への代事は許されない
If String.IsNullOrWhiteSpace(connectionString) Then
Throw New ArgumentException(“接続文字列が空です。”, NameOf(connectionString))
End If
Me._connectionString = connectionString
Me._commandTimeout = commandTimeout
End Sub
”’
”’
Public Function ExecuteQuery(ByVal query As String) As DataTable
Dim dt As New DataTable()
‘ Usingブロックによる確実なリソース解放
Using connection As New SqlConnection(Me._connectionString)
Using command As New SqlCommand(query, connection)
command.CommandTimeout = Me._commandTimeout
Using adapter As New SqlDataAdapter(command)
connection.Open()
adapter.Fill(dt)
End Using
End Using
End Using
Return dt
End Function
End Class
なぜ `ReadOnly` なのか?
もし `_connectionString` が通常の変数(`Dim` やプロパティの `Set` 公開)であった場合、クラス内のどこかのバグったメソッドが誤って値を書き換えてしまい、2回目以降のクエリが全く関係ないDBに飛ぶという「原因特定が極めて困難なバグ」を生むリスクがある。`ReadOnly` はそれをコンパイルレベルで完全に防ぐ。
—
4. 【Static】ローカル変数の寿命をハックする —— メソッドを超えた状態保持
最後に `Static` だ。VB.NET(およびC#の `static` とは意味が異なるので注意)における `Static` は、「メソッド内のローカル変数でありながら、メソッドが終了しても値を保持し続け、次回呼び出し時に引き継がれる」という特殊なライフサイクルを持つ。
グローバル変数(モジュールレベル変数)を使うと、どのメソッドから書き換えられたか追えなくなるという最悪の設計になる。しかし、「特定のメソッド内だけで使いたいが、前回の実行結果を記憶させたい(キャッシュや採番など)」という要件には `Static` が最適解となる。
⚠️ 危険な罠:マルチスレッド環境での注意点
`Static` 変数はアプリケーション内で唯一のインスタンスとして共有されるため、マルチスレッド環境やASP.NET等のWebアプリケーションで安易に使うと、スレッド競合(レースコンディション)によるデータ破損の温床になる。
業務自動化ツールであっても、バックグラウンドワーカー(`BackgroundWorker` や `Task`)を使う場合は細心の注意が必要だ。
実務的プロダクションコード例:安全な簡易キャッシュ・カウンター
シングルスレッド基盤のデスクトップアプリや、ログ出力のシーケンス番号採番などの実例を見てみよう。
.net
Public Class ProcessLogger
”’
”’
Public Sub LogWithSequence(ByVal message As String)
‘ Staticにより、メソッドが終了しても値はメモリ上に保持され続ける
Static sequenceNumber As Long = 0
‘ スレッドセーフティを考慮し、競合を防ぐ(マルチスレッド対策)
SyncLock GetType(ProcessLogger)
sequenceNumber += 1
Dim logLine As String = $”[{sequenceNumber,5:D5}] {DateTime.Now:yyyy-MM-dd HH:mm:ss} – {message}”
‘ ファイル出力などの処理
System.Diagnostics.Debug.WriteLine(logLine)
End SyncLock
End Sub
End Class
アーキテクトの視点:
「クラスのインスタンスをいくつ作っても、そのメソッド間で共有したい状態がある」場合、安易にモジュール(`Module`)やグローバル変数に走るな。`Static` 変数+適切な排他制御(`SyncLock`)を組み合わせることで、カプセル化を破壊せずに状態を維持できる。
—
5. まとめ:堅牢な業務ロジックを組むための設計哲学
VB.NETでのコーディングにおいて、変数を宣言する際は常に以下の思考プロセスを義務付けてほしい。
1. 「こいつの値は、アプリケーションの寿命を通じて1ミリも変わらないか?」
⇒ YESなら `Const` を使え。
2. 「インスタンスの生成時に決まり、その後はイミュータブル(不変)であるべきか?」
⇒ YESなら `ReadOnly` を使え。(実務ではこれが一番多い)
3. 「スコープはメソッド内に閉じたいが、状態だけを永続化させたいか?」
⇒ YESなら `Static` を検討せよ。(ただしスレッドセーフティに注意)
4. 上記どれにも該当しない通常の変数
⇒ 最も狭いスコープで `Dim` を使え。
この選択の基準を持つだけで、君が書くコードの品質は劇的に跳ね上がり、「原因不明のバグ」に怯える夜とは永久に決別できる。
コードは単に動くだけではゴミ同然だ。「変更に強く、誤った使い方ができない構造(堅牢性)」こそが、プロフェッショナルなエンジニアの証である。今日の設計から、さっそく取り入れてみてほしい。
