【入門編】Memory Leak(メモリリーク)の温床を断つ:Windows Formsにおけるイベントハンドラの適切な解除(-=演算子)とDisposeパターンの実装 – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

こんにちは!Windows Formsでのデスクトップアプリケーション開発、日々楽しんでいますか?

マクロの記録から一歩踏み出し、「自分でゼロから画面を作るぞ!」と意気込んでフォームを増やしていくと、避けて通れないのが「画面の閉じ方とメモリの管理」です。

「あれ? サブ画面を何度も開いたり閉じたりしていたら、なんだかパソコンの動作が重くなってきたぞ……?」
そんな現象に直面したことはありませんか?

今回は、Visual Basic .NET(VB.NET)のWindows Forms開発において、メモリリーク(メモリの消し忘れ)の最大の温床となる「イベントハンドラの罠」と、それを美しく断ち切るための「正しいDisposeパターン」について、現場の知見をたっぷり込めて優しく解説します。

ここをクリアすれば、あなたの書くコードは「プロのアーキテクト水準」にグッと近づきますよ。さあ、一緒に本質をマスターしましょう!

—

1. なぜメモリリークが起きるのか?(イベントの「見えない手」)

Windows Formsでは、ボタンをクリックしたときの処理(`Click`イベントなど)を、以下のように結びつけますよね。

‘ AddHandlerを使ったイベントの紐付け例
AddHandler subForm.MyEvent, AddressOf OnMyEventHandler

ここでVB.NETの裏側の世界を覗いてみましょう。
イベントを登録するということは、「発行元(サブ画面など)」が「受け手(メイン画面など)」のメモリアドレスをガッチリと保持するということです。

イメージ図で考えてみましょう。

[ メイン画面 (受け手) ] <-------(デリゲートという見えない手)------- [ サブ画面 (発行元) ] あなたがボタンを押してサブ画面を `Close()` や `Dispose()` で閉じ、画面上の見た目を消したとします。 しかし、イベントの紐付け(`AddressOf`)を解除しないままでいると、発行元であるサブ画面の中に「受け手への参照(デリゲート)」が残ってしまいます。

結果どうなるか?
「まだメイン画面から使われているかもしれないから……」と優しすぎるガベージコレクション(GC)が働き、サブ画面がメモリ上に居座り続けてしまうのです。これが、画面を閉じるたびにメモリが削られていく「メモリリーク」の正体です。

—

2. 犯人は君だ! `-= 演算子`(または `RemoveHandler`)による解除

このゾンビのようなメモリ残留を防ぐにはどうすればよいか?
答えは簡単です。「用事が済んだら、繋いだ手を自分でほどく」。これだけです。

VB.NETでは、イベントを紐付けるときは `+=`(または `AddHandler`)を使いますが、解除するときは `-=`(または `RemoveHandler`)を使います。

特に、頻繁に開閉するサブ画面やカスタムコントロールを作る場合は、画面が閉じられるタイミング(`Form_FormClosed` イベントなど)で必ず解除するのが鉄則です。

悪い例:イベントを繋ぎっぱなしにするコード

‘ 【NGパターン】これだとサブ画面がメモリから消えません!
Public Class Mainform
Private Sub OpenSubButton_Click(sender As Object, e As EventArgs) Handles OpenSubButton.Click
Dim subF As New SubForm()
‘ イベントを購読するが…解除する場所がない!
AddHandler subF.DataUpdated, AddressOf SubForm_DataUpdated
subF.Show()
End Sub

Private Sub SubForm_DataUpdated(sender As Object, e As EventArgs)
‘ データの更新処理
End Sub
End Class

良い例:責任を持ってイベントを解除するコード

‘ 【OKパターン】ライフサイクルに合わせて綺麗に片付ける
Public Class Mainform
Private WithEvents _subForm As SubForm

Private Sub OpenSubButton_Click(sender As Object, e As EventArgs) Handles OpenSubButton.Click
_subForm = New SubForm()
‘ 購読する
AddHandler _subForm.DataUpdated, AddressOf SubForm_DataUpdated
AddHandler _subForm.FormClosed, AddressOf SubForm_FormClosed

_subForm.Show()
End Sub

Private Sub SubForm_DataUpdated(sender As Object, e As EventArgs)
‘ データの更新処理
End Sub

‘ 画面が完全に閉じられたタイミングで確実にイベントを剥がす!
Private Sub SubForm_FormClosed(sender As Object, e As FormClosedEventArgs)
Dim frm = DirectCast(sender, SubForm)

‘ 解除の儀式
RemoveHandler frm.DataUpdated, AddressOf SubForm_DataUpdated
RemoveHandler frm.FormClosed, AddressOf SubForm_FormClosed

‘ 参照も断ち切る
_subForm = Nothing
End Sub
End Class

—

3. さらに安全に!カスタムコントロールにおける `Dispose` パターンの実装

サブ画面だけでなく、自作のユーザーコントロール(UserControl)を画面上で何度も生成・破棄する場合も同様の配慮が必要です。

Windows Formsのコントロールには、破棄されるときに必ず呼ばれる `Dispose` メソッド が用意されています。このメソッドをオーバーライド(上書き)して、内部で購読しているイベントをごっそり掃除するのがプロの作法です。

以下の実装パターンをそのままテンプレートとしてあなたの引き出しに入れておいてください。

Public Class MyCustomControl
Inherits UserControl

‘ 内部で保持している外部イベントや重いリソースがあると仮定
Public Event StatusChanged As EventHandler

Public Sub New()
‘ この呼び出しは Windows フォーム デザイナーで必要です。
InitializeComponent()

‘ 例:アプリケーション全体のマネージャー等からイベントを購読している場合
AddHandler ApplicationState.GlobalEvent, AddressOf GlobalEvent_Handler
End Sub

Private Sub GlobalEvent_Handler(sender As Object, e As EventArgs)
‘ 処理
RaiseEvent StatusChanged(Me, EventArgs.Empty)
End Sub

‘ ==========================================================
‘ ここが極限までメモリリークを防ぐ Dispose パターンの実体
‘ ==========================================================
Protected Overrides Sub Dispose(disposing As Boolean)
Try
If disposing Then
‘ 1. マネージリソース(他のオブジェクトへの参照、イベントハンドラ)の解放
‘ ※ここで「繋いだ手をすべてほどく」作業を行います
RemoveHandler ApplicationState.GlobalEvent, AddressOf GlobalEvent_Handler

If components IsNot Nothing Then
components.Dispose()
End If
End If

‘ 2. アンマネージリソース(COMオブジェクトやファイルハンドル等)があればここで解放

Finally
‘ 3. 最後に必ず基底クラスの Dispose を呼ぶ
MyBase.Dispose(disposing)
End Try
End Sub
End Class

—

4. まとめ:コードの「始末」まで美しく書けるエンジニアへ

今回は、Windows Formsにおけるメモリリークの温床と、イベントの適切な解除、そして `Dispose` パターンについて解説しました。

  • イベントを紐付けたら(`AddHandler` / `+=`)、必ず解除する(`RemoveHandler` / `-=)癖をつける。
  • 画面やカスタムコントロールが閉じられるタイミング(`FormClosed` や `Dispose`)を意識する。
  • 「作ったら責任を持って片付ける」というオブジェクトのライフサイクルを愛する。

動くコードを書くだけなら初心者でもすぐにできますが、「長時間起動し続けてもメモリを食いつぶさない、美しいアプリ」を作れるかどうかが、エンジニアとしての腕の見せ所です。

ここをクリアしたあなたなら、もうマクロの記録の枠組みからは完全に卒業し、堅牢なアーキテクチャを設計するプロフェッショナルの仲間入りです。
ぜひ、今開発しているアプリケーションのコードを見直して、不要なイベントの結びつきがないかチェックしてみてくださいね。応援しています!

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