VB.NETで設定変更を「リアクティブ」に掌握せよ:Rx.NETで実現する疎結合な監視アーキテクチャ
現場でよく見る「設定変更」のコード。タイマーで定期的にファイルを読み込んだり、イベントハンドラーをネストさせて複雑なフラグ管理をしたり……そんな非効率な設計に終止符を打ちましょう。
VB.NETにおける業務自動化の極致は、「状態の変化」をいかに美しく、かつ副作用なくストリームとして処理できるかにかかっています。今日は、`IObservable`とReactive Extensions (Rx) を用いて、設定変更をリアクティブに監視する「プロの設計」を伝授します。
—
1. なぜ「イベントハンドラー」ではダメなのか
従来の `FileSystemWatcher` やフラグチェックでは、以下の問題が発生しがちです。
- 状態の爆発: 「読み込み中」「更新中」といったフラグ変数がクラス内に散らばり、デバッグ不可能なスパゲッティコードになる。
- 競合リスク: ファイルシステムからのイベントはマルチスレッドで飛んでくる。スレッドセーフを考慮しない単純な実装は、アプリケーションをクラッシュさせる。
- 保守性の欠如: 「設定変更→バリデーション→適用」というパイプラインが、メソッドのあちこちに散逸する。
Rx.NETを使えば、「設定変更というストリーム」を一つのパイプラインとして定義し、宣言的に記述することができます。
—
2. 実装:FileSystemWatcherをRxで包み込む
まずは、`FileSystemWatcher`を`IObservable`に変換し、設定ファイルの変更をリアクティブに検知するエンジンを作りましょう。
プロダクションコード例
Imports System.IO
Imports System.Reactive.Linq
Imports System.Reactive.Subjects
Public Class ConfigWatcher
‘ 変更通知を購読するためのSubject
Private ReadOnly _configChanged As New Subject(Of String)()
Private _watcher As FileSystemWatcher
Public ReadOnly Property ConfigChanged As IObservable(Of String)
Get
Return _configChanged.AsObservable()
End Get
End Property
Public Sub New(filePath As String)
Dim directory = Path.GetDirectoryName(filePath)
Dim fileName = Path.GetFileName(filePath)
_watcher = New FileSystemWatcher(directory, fileName) With {
.NotifyFilter = NotifyFilters.LastWrite,
.EnableRaisingEvents = True
}
‘ イベントをRxストリームへ変換 (Throttleでチャタリング対策)
Observable.FromEventPattern(Of FileSystemEventHandler, FileSystemEventArgs)(
Sub(h) AddHandler _watcher.Changed, h,
Sub(h) RemoveHandler _watcher.Changed, h
) _
.Select(Function(e) e.EventArgs.FullPath) _
.Throttle(TimeSpan.FromMilliseconds(500)) _ ‘ 連続書き込み対策
.Subscribe(Sub(path) _configChanged.OnNext(path))
End Sub
End Class
この設計の肝
- Throttle (間引き): ファイル保存時に発生する「複数回イベント」を、500ms待機することで単一のイベントに集約します。これでリソースの無駄遣いを防ぎます。
- Subjectの隠蔽: 外部には `IObservable` のみを公開し、勝手にイベントを発生させられないようカプセル化しています。
—
3. 設定変更パイプラインの構築
監視を開始したら、次は「変更されたらどうするか」を記述します。ここがRxの真骨頂です。
‘ メイン処理側での購読
Dim watcher As New ConfigWatcher(“C:\App\settings.json”)
watcher.ConfigChanged
.ObserveOn(System.Threading.SynchronizationContext.Current) ‘ UIスレッドに戻す
.Select(Function(path) LoadSettings(path)) ‘ 設定読み込み
.Where(Function(config) config IsNot Nothing) ‘ バリデーションチェック
.Subscribe(
Sub(config)
‘ ここでアプリケーションの設定を更新
Console.WriteLine(“設定が更新されました: ” & config.Version)
End Sub,
Sub(ex)
‘ エラーハンドリングを一箇所で集約
Logger.Error(“設定反映中にエラー発生”, ex)
End Sub
)
—
4. 業務自動化エンジニアが守るべき「3つの鉄則」
実務でこの設計を導入する際、必ず以下の鉄則を守ってください。
1. ObserveOnによるスレッド制御:
ファイルシステムイベントはバックグラウンドスレッドで発生します。UI(WinForms/WPF)のプロパティを直接触ると例外になります。必ず `.ObserveOn(SynchronizationContext.Current)` を通してスレッドを安全に切り替えてください。
2. Disposeの徹底:
`FileSystemWatcher` はOSのリソースを占有します。`IDisposable` を実装し、アプリケーション終了時には必ず `_watcher.Dispose()` を呼び出すようにしてください。メモリリークは「見えないバグ」の元凶です。
3. バリデーションはパイプラインの初期に行う:
`.Where()` を使って、不正な設定ファイルを後続のプロセスに流さないようにしてください。これにより、メインロジックは常に「正しい設定」を前提に記述できるようになります。
—
終わりに:ツールを「作品」に変えるために
VB.NETは「古い言語」ではありません。Rx.NETのようなモダンな設計パラダイムを組み合わせることで、堅牢でメンテナンス性の高い、プロフェッショナルなツールへと進化します。
「動けばいい」コードを書くのは素人です。「変更に強い、論理的に破綻のない流れ」を作るのがエンジニアの仕事です。まずはこの `ConfigWatcher` をコピーして、あなたのプロジェクトの「設定管理」をリアクティブに書き換えてみてください。その瞬間に、あなたのコードの品質は一段階上のステージへ到達するはずです。
