【入門編】VB.NETにおけるDisposableパターンとFinalizeの正しい実装:アンマネージドリソース(COMオブジェクトやWin32ハンドル)を確実にかつ安全に解放するためのベストプラクティス – Visual Basic (VB / VB.NET)解析バイブル

スポンサーリンク

こんにちは!Windows Formsアプリケーションの開発現場で、日夜UIと格闘されていることと思います。

「ExcelのVBAや古いVB6の感覚で書いていたら、なぜかメモリ使用量が減らない…」
「Windows Formsアプリを長時間起動していると、だんだん動作が重くなって、最終的にフリーズする…」

そんな恐怖に直面したことはありませんか?
特に、COMオブジェクト(Excel操作など)やWin32 APIのハンドルを扱うようになると、ガベージコレクタ(GC)だけでは太刀打ちできない領域が出てきます。ここをクリアすれば、あなたのVB.NETスキルは「初心者」から「現場をリードするプロフェッショナル」へと確実にステップアップしますよ。

今回は、VB.NETにおける「DisposableパターンとFinalizeの正しい実装」について、プロのアーキテクトの視点から徹底的に分かりやすく解説します。

—

1. なぜ「GC(ガベージコレクタ)」だけでは不十分なのか?

VB.NET(.NET Framework / .NET Core)の世界には、使わなくなったメモリを勝手に掃除してくれる優秀な掃除屋さん、ガベージコレクタ(GC)がいます。

「じゃあ、オブジェクトの解放なんてGCに全部お任せでいいんじゃないの?」

そう思いますよね。マネージドリソース(VB.NETのコードだけで作られた純粋なオブジェクト)だけであれば、それで正解です。しかし、以下のアンマネージドリソースが絡むと話が変わります。

  • COMオブジェクト(例: `Microsoft.Office.Interop.Excel` など)
  • Win32 APIのハンドル(ファイルハンドル、ウィンドウハンドル、DCなど)
  • GDI+のグラフィックスリソース(ブラシ、ペン、ビットマップなど)

これらは.NETの管理外にある「OSのネイティブ資源」です。GCは「メモリが足りなくなったら掃除する」という気まぐれなタイミングで動くため、「今すぐOSにハンドルを返したい!」というデリケートなタイミングを制御できません。 結果として、メモリリークや「ファイルが使用中で削除できません」といった厄介なエラーを引き起こすのです。

—

2. 決定打となる設計:「Disposableパターン」の全体像

この問題をエレガントに解決するのが、Microsoftが推奨するDisposableパターンです。

これは、以下の2つのアプローチを組み合わせた、鉄壁のライフサイクル管理手法です。
1. プログラマの意志で今すぐ綺麗に片付ける(`IDisposable.Dispose`)
2. 万が一、片付け忘れがあったときの最終安全装置(ファイナライザ `Finalize`)

まずは、Windows Formsの画面や業務クラスでそのまま使える、完璧な実装テンプレートを頭に叩き込みましょう。

黄金のコードテンプレート

Imports System.Runtime.InteropServices

Public Class NativeResourceWrapper
Implements IDisposable

‘ アンマネージドハンドル(例として IntPtr を使用)
Private _nativeHandle As IntPtr
‘ マネージドリソース(他のIDisposable実装オブジェクトなど)
Private _managedResource As System.IO.StreamReader

‘ フラグ:すでにDisposeが呼ばれたかどうかを二重実行から守る
Private _disposed As Boolean = False

Public Sub New(filePath As String)
‘ 適当なマネージドリソースの初期化
_managedResource = New System.IO.StreamReader(filePath)

‘ 擬似的にアンマネージドハンドルを取得したと仮定
‘ 実際には Win32 API の CreateFile などを呼び出すイメージです
_nativeHandle = Marshal.AllocHGlobal(1024)
End Sub

‘ ==========================================
‘ 1. 公開された Dispose メソッド (IDisposableの実装)
‘ ==========================================
Public Sub Dispose() Implements IDisposable.Dispose
‘ 実際の解放処理を呼び出す (disposing = True は「手動で今すぐ片付けて」の意味)
Dispose(disposing:=True)

‘ 「もうGCのファイナライザを呼ばなくていいよ!」とOS/CLRに伝える
‘ これにより、不要なファイナライザの実行コスト(パフォーマンス低下)を防ぎます
GC.SuppressFinalize(Me)
End Sub

‘ ==========================================
‘ 2. 実際の解放ロジックを集約する保護されたメソッド
‘ ==========================================
Protected Overridable Sub Dispose(disposing As Boolean)
If Not _disposed Then
If disposing Then
‘ ————————————————–
‘ 【マネージドリソースの解放】
‘ ————————————————–
‘ disposing が True の時だけ実行する(GCから呼ばれた時は実行しない)
If _managedResource IsNot Nothing Then
_managedResource.Dispose()
_managedResource = Nothing
End If
End If

‘ ————————————————–
‘ 【アンマネージドリソースの解放】
‘ ————————————————–
‘ disposing の値に関わらず、必ず実行する(ハンドルやメモリのリークを防ぐ)
If _nativeHandle <> IntPtr.Zero Then
Marshal.FreeHGlobal(_nativeHandle)
_nativeHandle = IntPtr.Zero
End If

_disposed = True
End If
End Sub

‘ ==========================================
‘ 3. ファイナライザ(デストラクタ)
‘ ==========================================
‘ プログラマが Dispose を呼び忘れてオブジェクトが消滅する際、GCによって最後に呼ばれます
Protected Overrides Sub Finalize()
‘ disposing = False として呼び出す
Dispose(disposing:=False)
MyBase.Finalize()
End Sub

End Class

—

3. コードの急所を読み解く:3つの重要ポイント

初学者のうちや、VBAから移行したばかりのエンジニアが混乱しやすいポイントを、先輩目線で優しく解説します。

ポイント①:なぜ `Dispose(disposing As Boolean)` を分けるのか?

コードの中に `Dispose()` が2つあるように見えて混乱しませんか?

  • `Public Sub Dispose()`: 外部から呼ばれる窓口です。
  • `Protected Overridable Sub Dispose(disposing As Boolean)`: 実務をこなす本体です。

なぜ分けるかと言うと、「手動で片付ける時」と「GCに勝手に回収される時」で、解放していい対象が違うからです。
GCに回収される(ファイナライザから呼ばれる)時、他のマネージドオブジェクト(`_managedResource`など)は、すでにGCによって勝手に順序不同で破壊されている可能性があります。そのため、ファイナライザからマネージドオブジェクトに触ると、予測不能なNullReferenceExceptionやメモリ破壊を起こします。これが「GC任せにしてはいけない」最大の理由です。

ポイント②:`GC.SuppressFinalize(Me)` の魔法

`Dispose()` の中で呼んでいる `GC.SuppressFinalize(Me)`。これ、めちゃくちゃ重要です。
手動で綺麗に掃除が終わったのだから、「わざわざ後からファイナライザ(Finalize)を動かさないでね!」とGCに予約を取り消す命令です。これを挟むことで、アプリケーションのパフォーマンス低下を防ぐことができます。

ポイント③:Using構文で美しく自動化する

わざわざ毎回 `obj.Dispose()` と書くのは面倒ですし、途中でエラー(例外)が発生した時に解放処理がスキップされてしまいます。そこでVB.NETの `Using` ステートメント を使います。

‘ Using ブロックを抜けた瞬間に、自動的に安全に Dispose() が呼ばれます!
Using wrapper As New NativeResourceWrapper(“C:\temp\sample.txt”)
‘ ここで処理を行う
Console.WriteLine(“ファイル処理中…”)
End Using ‘ ← ここで確実にリソースが解放される!

—

4. 現場でやりがちな「痛いミス」(アンチパターン)

最後に、実際の開発現場でよく見かける「やってはいけない実装」をご紹介します。

  • × ファイナライザだけを書いて満足する
  • → ファイナライザに頼ると、リソースが解放されるタイミングがいつになるか分かりません。ファイルが開きっぱなしになる原因になります。必ず `IDisposable` を実装し、手動で即座に解放できるようにしましょう。
  • × 例外が発生して Dispose がバイパスされる
  • → `Try … Catch` を使う場合、`Finally` ブロックの中で明示的に `Dispose()` を呼ぶか、先ほどの `Using` 構文を必ず使いましょう。

—

まとめ

いかがでしたでしょうか?
Visual Basicでのアンマネージドリソース管理は、一見すると難しく感じるかもしれませんが、「Disposableパターンのテンプレートをしっかり理解し、Usingで囲む」という基本形さえ守れば、怖くありません。

ここをクリアすれば、長期間稼働してもメモリが安定し、クラッシュしない強靭なWindows Formsアプリケーションを構築できるようになります。

「ここをもっと詳しく知りたい」「Excel連携時のCOM解放について聞いてみたい」などがあれば、いつでも気軽にエンジニア仲間として相談してくださいね。あなたのVB.NETライフを応援しています!

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