【実務・中級編】複数画面間における疎結合なデータ共有:WeakEventManagerを活用したメモリリークフリーなオブザーバーパターン – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

疎結合の罠:なぜあなたのVB.NETアプリはメモリを食い尽くすのか

業務アプリケーションを構築していると、必ず直面する壁がある。「画面Aで保存したデータを、画面Bに即座に反映させたい」という要件だ。

多くの開発者はここで、安易に親画面や静的プロパティにイベントを直結させる。あるいは、`AddHandler` でイベントを購読し、そのまま放置する。これが、「メモリリーク」という名の時限爆弾を仕込んでいることに気づかないまま。

VB.NETにおけるイベント購読は、「強い参照(Strong Reference)」だ。購読者が生きている限り、発信者もGC(ガベージコレクション)の対象から外れる。画面を閉じてもインスタンスがメモリに残り続け、操作を繰り返すたびにアプリは重くなり、やがてクラッシュする。

本記事では、この悪夢を断ち切るための「WeakEventManagerによるオブザーバーパターン」という解法を授ける。

—

なぜ従来のイベント設計ではいけないのか

従来の「イベントを購読して、`Dispose`で`RemoveHandler`する」手法は、人間が管理する限り必ず綻びが生じる。

  • `Dispose` を呼び忘れた。
  • 例外発生時に `RemoveHandler` がスキップされた。
  • 複雑なUIツリーの中で、誰が購読終了の責任を持つのか不明確。

これらを「運用」でカバーするのはプロの仕事ではない。構造的に解決すべきだ。

—

解決策:WeakEventManagerの採用

`WeakEventManager` は、イベントソースに対して「弱い参照」を保持する。ソース側が購読者の存在を気にしないため、購読者がGCの対象になれば、自然と購読関係も消滅する。これがメモリリークフリーな疎結合アーキテクチャの核心だ。

実装コード:汎用イベントバス

まずは、アプリケーション全体で使える「メッセージングのハブ」を作成する。

Imports System.Windows

‘ 画面間で受け渡すデータの型
Public Class DataChangedMessage
Public Property Payload As String
End Class

‘ メッセージングの基盤となるイベントソース
Public Class DataService
‘ イベントの定義(通常のDelegate)
Public Shared Event DataChanged As EventHandler(Of DataChangedMessage)

Public Shared Sub NotifyDataChanged(payload As String)
‘ ここでイベントを発行
RaiseEvent DataChanged(Nothing, New DataChangedMessage With {.Payload = payload})
End Sub
End Class

実装コード:購読側(メモリリークフリーな設計)

次に、このイベントを購読する画面側を実装する。ここで `WeakEventManager` の出番だ。

Public Class ReceiverForm
Inherits Form
Implements IWeakEventListener

Public Sub New()
‘ イベントを購読(WeakEventManager経由で登録)
‘ これにより、このフォームが破棄されてもメモリリークしない
WeakEventManager(Of DataService, DataChangedMessage).AddHandler(
AddressOf DataService.NotifyDataChanged, Me)
End Sub

‘ IWeakEventListenerの実装:イベント発生時にここが呼ばれる
Public Function ReceiveWeakEvent(managerType As Type, sender As Object, e As EventArgs) As Boolean Implements IWeakEventListener.ReceiveWeakEvent
If managerType Is GetType(WeakEventManager(Of DataService, DataChangedMessage)) Then
Dim msg = DirectCast(e, DataChangedMessage)
‘ 画面更新処理
Me.Label1.Text = msg.Payload
Return True
End If
Return False
End Function
End Class

—

運用上の注意点とアーキテクチャの真実

1. 「静的」なデータ連携の注意点

上記の例は `Shared` (静的) なイベントソースを使用している。これは非常に強力だが、同時に「どこからでも変更できる」というリスクも孕む。業務ロジック(データベース連携など)を記述する場合は、必ずサービス層を一枚噛ませ、直接UIがデータベースを叩かない設計を徹底すること。

2. スレッドモデルの意識

`WeakEventManager` はUIスレッド以外からの発行も受け付ける。もし `DataService` がバックグラウンドスレッドで動作している場合、`ReceiveWeakEvent` 内でUIコンポーネントを直接操作すると例外(クロススレッドエラー)が発生する。その場合は、`Me.Invoke(Sub() …)` を介してUIスレッドに同期させることを忘れてはならない。

3. なぜ今、このパターンなのか

現代の業務アプリにおいて、スピードよりも優先されるのは「継続的な保守性」だ。画面が10個、20個と増えたとき、従来の強い参照による結合は開発者を地獄へ引きずり込む。WeakEventManagerによる設計は、画面間を「疎」に保つための、いわば「関心の分離」の極致である。

—

結びに:伝説のエンジニアとしてのアドバイス

「とりあえず動くコード」を書くのはジュニアレベルの仕事だ。
我々シニアエンジニアは、「コードが消える瞬間(ライフサイクル)」を設計する。

メモリを浪費するアプリケーションは、美しくない。そして美しくないコードは、必ず後任者に負債として牙を剥く。今日紹介したパターンを自社フレームワークに組み込み、二度と「イベントの解除漏れ」というバグに工数を使わない環境を作ってほしい。

さあ、次は君たちがこの設計を基盤に、より堅牢なシステムを構築する番だ。健闘を祈る。

タイトルとURLをコピーしました