他の参照を破棄可能なタイミング
オブジェクトが別のオブジェクトを保持している場合、ファイナライザから保持するオブジェクトを操作することはできません。
class A { public void Open(); public void Close(); } class B { private A a; ~B() { if (a != null) { a.Close(); } } }
このようなコードは .NET では誤りです。B のファイナライザ ~B() を実行中に、すでに a はファイナライズされていて参照不可能である可能性があるためです。実際に .NET Framework v1.1.4322 の GC の実装では、このパターンでは常に a が先にファイナライズされます。
このような問題を解決するためにも、オブジェクトの手動ファイナライズは有用です。つまり、
class B : IDisposable { void DIspose() { if (a != null) { a.Close(); a = null; } GC.SupressFinalize(this); } }
このように、a がファイナライズされる前に a を操作する機会を得ることが可能ということです。かなり単純なことですが、ファイナライザを使い始めてすぐには中々気がつけない落とし穴でもあると思います。
ここで、2つ3つ疑問があるかもしれません。
- SupressFinalize って?
- A も IDisposable を実装して a.Close() を a.Dispose() にしないのか?
- Dispose() があれば ~B() はいらない?
最初の疑問はドキュメントでも読めばわかることなのですが、Dispose() で手動でファイナライズをおこなったオブジェクトは、もはや GC によるファイナライズは無用である場合が多いので、それを GC に対して伝える手段です。
もし、そのオブジェクトが参照を失わずに新たに利用可能になるならば、そのメソッドで ReRegisterForFinalize(this) を呼び出すこともできますが、そのようなメソッドのための IReusable とか ICreatable とかいったインターフェスはありません。手動破棄されたオブジェクトは GC によるいつか不明な回収によって十分再利用可能であると思われるので、シンプルに新しいオブジェクトを new すればよいだけですので、あえていうなら new が再生成のためのインターフェスに当たると思われます。
次の、A も IDisposable を実装するかどうかですが、もちろんそのように変更することは問題ありませんが、それは前半で書いたような理由で A が IDisposable の助けを必要とするかどうかで決定することであって、B からみた都合で決めることではありません。
3番目は、.NET デザインでは
~B() { dispose(false); } Dispose() { dispose(true); GC.SupressFinalize(this); } dispose(bool disposing) { if (disposing) { // : ...アンマネージドリソースや別の参照のための処理 } // : ...マネージドリソースのための処理 }
みたいな構造が推奨されていたと思います。Dispose() されないことが休息なアンマネージドリソースの枯渇や、致命的なリソースの長時間保持の可能性をもつような場合であれば、~B() では Assert() を呼び出してしまうなども考慮していいと思います。